Introduction to Shiro security framework

I. Shiro overview 1Shiro overview Shiro is an open source security framework under apache. It extracts the functions rel...

I. Shiro overview
1Shiro overview
Shiro is an open source security framework under apache. It extracts the functions related to security authentication of software system, realizes user identity authentication, authority authorization, encryption, session management and other functions, and forms a general security authentication framework. Using Shiro, you can quickly complete the development of authentication, authorization and other functions, and reduce the system cost.
2Shiro architecture

  1. Subject: subject object, which is responsible for submitting user authentication and authorization information.
  2. SecurityManager: Security Manager, responsible for the implementation of authentication, authorization and other services.
  3. Realm: domain object, which is responsible for obtaining business data from the data layer.
    3 Shiro detailed architecture
  4. Subject: a specific entity (user, third-party service, etc.) interacting with software.
  5. Security Manager: the core of Shiro, which is used to coordinate the work of management components.
  6. Authenticator: responsible for performing authentication operations.
  7. Authorization Manager: responsible for authorization detection.
  8. Session manager: responsible for creating and managing user session life cycle, providing a powerful session experience.
  9. SessionDAO: executes the session persistence (CRUD) action on behalf of the SessionManager, which allows any stored data to be attached to the session management foundation.
  10. CacheManager: provides the ability to create cache instances and manage cache lifecycles.
  11. Cryptography (encryption manager): provides the design and management of encryption methods.
  12. Realms (domain objects): a bridge between shiro and your application security data.
    II. Shiro framework authentication interception implementation (filter)
    1. Basic environment configuration of Shiro
    1.1 add dependency
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>

1.2Shiro core object configuration
Step 1: create the SpringShiroConfig class. The key codes are as follows:

package com.cy.pj.common.config; /**@Configuration The class described by the annotation is a configuration object, * This object is also left to spring management */ @Configuration public class SpringShiroConfig {//spring-shiro.xml }

Step 2: add SecurityManager configuration in Shiro configuration class. The key codes are as follows:

@Bean public SecurityManager securityManager() { DefaultWebSecurityManager sManager= new DefaultWebSecurityManager(); return sManager; }

Step 3: add the configuration of ShiroFilterFactoryBean object in Shiro configuration class. Use this object to set anonymous access and authenticated access to resources. The key codes are as follows:

@Bean public ShiroFilterFactoryBean shiroFilterFactory ( SecurityManager securityManager) { ShiroFilterFactoryBean sfBean= new ShiroFilterFactoryBean(); sfBean.setSecurityManager(securityManager); //Define map to specify request filtering rules (which resources allow anonymous access and which must be authenticated access) LinkedHashMap<String,String> map= new LinkedHashMap<>(); //Allow anonymous access to static resources: "anon" map.put("/bower_components/**","anon"); map.put("/modules/**","anon"); map.put("/dist/**","anon"); map.put("/plugins/**","anon"); //Except for anonymous resources, all other resources must be accessed after authentication ("authc") map.put("/**","authc"); sfBean.setFilterChainDefinitionMap(map); return sfBean; }

2. Specific business
After the server intercepts the user's request, it determines whether the request has been authenticated. If there is no authentication, it should jump to the login page first.
2.1 front end
Add a login.html page in / templates/pages /, then deploy the project to the web server and start the test run
2.2 background
Step 1: add a method to render the login page in PageController. The key codes are as follows:

@RequestMapping("doLoginUI") public String doLoginUI(){ return "login"; }

Step 2: modify the configuration of shirofilterfactory bean in SpringShiroConfig class and add the setting of login url. See sfBean.setLoginUrl("/doLoginUI") for key codes.

@Bean public ShiroFilterFactoryBean shiroFilterFactory ( @Autowired SecurityManager securityManager) { ShiroFilterFactoryBean sfBean= new ShiroFilterFactoryBean(); sfBean.setSecurityManager(securityManager); sfBean.setLoginUrl("/doLoginUI"); //Define the map to specify the request filtering rules (which resources allow anonymous access, Which must be authenticated) LinkedHashMap<String,String> map= new LinkedHashMap<>(); //Allow anonymous access to static resources: "anon" map.put("/bower_components/**","anon"); map.put("/modules/**","anon"); map.put("/dist/**","anon"); map.put("/plugins/**","anon"); //Except for anonymous resources, all other resources must be accessed after authentication ("authc") map.put("/**","authc"); sfBean.setFilterChainDefinitionMap(map); return sfBean; }

III. implementation of Shiro framework authentication service
3.1 certification process analysis

  1. The system calls the login method of the subject to submit the user information to the SecurityManager
  2. The SecurityManager delegates the authentication operation to the Authenticator object Authenticator
  3. The Authenticator passes the identity information entered by the user to Realm.
  4. Realm accesses the database to obtain user information, and then encapsulates and returns the information.
  5. The Authenticator authenticates the information returned by the realm.
    3.2 specific business
    front end
    Click login to asynchronously submit the entered user name and password to the server.
$(function () { $(".login-box-body").on("click",".btn",doLogin); }); function doLogin(){ var params={ username:$("#usernameId").val(), password:$("#passwordId").val() } var url="user/doLogin"; $.post(url,params,function(result){ if(result.state==1){ //Jump to the corresponding page of indexUI location.href="doIndexUI?t="+Math.random(); }else{ $(".login-box-msg").html(result.message); } }); }

backstage
dao layer
In the SysUserDao interface, add a method to obtain user objects according to user names. The key codes are as follows:

SysUser findUserByUserName(String username)

3.2.3 Mapper element definition

<select id="findUserByUserName" resultType="com.cy.pj.sys.entity.SysUser"> select * from sys_users where username=# </select>

service layer
Step 1: define shirosuserrealm class. The key codes are as follows:

package com.cy.pj.sys.service.realm; @Service public class ShiroUserRealm extends AuthorizingRealm { @Autowired private SysUserDao sysUserDao; /** * Set the credential matcher (use the same encryption algorithm as the user add operation) */ @Override public void setCredentialsMatcher( CredentialsMatcher credentialsMatcher) { //Build voucher matching object HashedCredentialsMatcher cMatcher= new HashedCredentialsMatcher(); //Set encryption algorithm cMatcher.setHashAlgorithmName("MD5"); //Set encryption times cMatcher.setHashIterations(1); super.setCredentialsMatcher(cMatcher); } /** * This method completes the acquisition and packaging of authentication data, and the system * The bottom layer will transfer the authentication data to the authentication manager for authentication * The manager completes the authentication operation. */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //1. Get user name (user page input) UsernamePasswordToken upToken= (UsernamePasswordToken)token; String username=upToken.getUsername(); //2. Query user information based on user name SysUser user= sysUserDao.findUserByUserName(username); //3. Determine whether the user exists if(user==null) throw new UnknownAccountException(); //4. Determine whether the user has been disabled. if(user.getValid()==0) throw new LockedAccountException(); //5. Encapsulate user information ByteSource credentialsSalt= ByteSource.Util.bytes(user.getSalt()); //Remember: what object to build depends on the return value of the method SimpleAuthenticationInfo info= new SimpleAuthenticationInfo( user,//principal (identity) user.getPassword(),//hashedCredentials credentialsSalt, //credentialsSalt getName());//realName //6. Return encapsulation result return info;//The return value is passed to the authentication manager (later) //The authentication manager will use this information to complete the authentication operation) } .... }

Step 2: for this realm, you need to inject it into the securitymanager object in the SpringShiroConfig configuration class and modify the securitymanager method, for example:

@Bean public SecurityManager securityManager(Realm realm) { DefaultWebSecurityManager sManager= new DefaultWebSecurityManager(); sManager.setRealm(realm); return sManager; }

control layer
Step 1: add login processing methods in SysUserController. The key codes are as follows:

@RequestMapping("doLogin") public JsonResult doLogin(String username,String password){ //1. Get Subject object Subject subject=SecurityUtils.getSubject(); //2. Submit user information through Subject and submit it to shiro framework for authentication //2.1 encapsulation of users UsernamePasswordToken token= new UsernamePasswordToken( username,//Identity information password);//Voucher information //2.2 identity authentication of user information subject.login(token); //analysis: //1) The token will be passed to shiro's SecurityManager //2) The securitymanager passes the token to the authentication manager //3) The authentication manager passes the token to the realm return new JsonResult("login ok"); }

Step 2: modify the configuration of shiroFilterFactory and configure the path / user/doLogin for anonymous access. See the code marked in yellow below:

@Bean public ShiroFilterFactoryBean shiroFilterFactory ( SecurityManager securityManager) { ShiroFilterFactoryBean sfBean= new ShiroFilterFactoryBean(); sfBean.setSecurityManager(securityManager); //If there is no authentication request, access the url of this authentication first sfBean.setLoginUrl("/doLoginUI"); //Define map to specify request filtering rules (which resources allow anonymous access and which must be authenticated access) LinkedHashMap<String,String> map= new LinkedHashMap<>(); //Allow anonymous access to static resources: "anon" map.put("/bower_components/**","anon"); map.put("/build/**","anon"); map.put("/dist/**","anon"); map.put("/plugins/**","anon"); map.put("/user/doLogin","anon"); //Authc indicates that all resources except those accessed anonymously can only be accessed after authentication ("authc") map.put("/**","authc"); sfBean.setFilterChainDefinitionMap(map); return sfBean; }

Step 3: in order to improve the user experience during login, we can handle the exception information in the system. For example, add the following methods to the unified exception handling class:

@ExceptionHandler(ShiroException.class) @ResponseBody public JsonResult doHandleShiroException( ShiroException e) { JsonResult r=new JsonResult(); r.setState(0); if(e instanceof UnknownAccountException) { r.setMessage("Account does not exist"); }else if(e instanceof LockedAccountException) { r.setMessage("Account has been disabled"); }else if(e instanceof IncorrectCredentialsException) { r.setMessage("Incorrect password"); }else if(e instanceof AuthorizationException) { r.setMessage("You do not have permission for this operation"); }else { r.setMessage("System maintenance in progress"); } e.printStackTrace(); return r; }

Step 4: exit operation configuration implementation
In the SpringShiroConfig configuration class, modify the filtering rules

@Bean public ShiroFilterFactoryBean shiroFilterFactory( SecurityManager securityManager) { ShiroFilterFactoryBean sfBean= new ShiroFilterFactoryBean(); sfBean.setSecurityManager(securityManager); //If there is no authentication request, access the url of this authentication first sfBean.setLoginUrl("/doLoginUI"); //Define map to specify request filtering rules (which resources allow anonymous access and which must be authenticated access) LinkedHashMap<String,String> map=new LinkedHashMap<>(); //Allow anonymous access to static resources: "anon" map.put("/bower_components/**","anon"); map.put("/build/**","anon"); map.put("/dist/**","anon"); map.put("/plugins/**","anon"); map.put("/user/doLogin","anon"); map.put("/doLogout","logout"); //Except for anonymous resources, all other resources must be accessed after authentication ("authc") map.put("/**","authc"); sfBean.setFilterChainDefinitionMap(map); return sfBean; }

IV. implementation of Shiro framework authorization process
The authorization process is analyzed as follows:

  1. The system calls the subject related methods to submit the user information (such as isPermitted) to the SecurityManager.
  2. The SecurityManager delegates the permission detection operation to the Authorizer object.
  3. The Authorizer delegates user information to realm.
  4. Realm accesses the database to obtain user permission information and encapsulates it.
  5. The Authorizer determines the user authorization information.
    Add authorization configuration:
    When the advisor object is configured, the shiro framework will decide whether to create a proxy object through the return value of the matches method of this object (similar to the pointcut) for permission control.
@Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor ( SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor= new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }explain:The most important rule to respect when using a framework,The framework rules specify how to use it.

service layer: modify the doGetAuthorizationInfo method in shirosuserrealm class. The key code is as follows:

@Service public class ShiroUserRealm extends AuthorizingRealm { @Autowired private SysUserDao sysUserDao; @Autowired private SysUserRoleDao sysUserRoleDao; @Autowired private SysRoleMenuDao sysRoleMenuDao; @Autowired private SysMenuDao sysMenuDao; /**This method completes the acquisition and encapsulation of authorization information*/ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { //1. Obtain login user information, such as user id SysUser user=(SysUser)principals.getPrimaryPrincipal(); Integer userId=user.getId(); //2. Obtain user owned roles based on user id (sys_user_roles) List<Integer> roleIds= sysUserRoleDao.findRoleIdsByUserId(userId); if(roleIds==null||roleIds.size()==0) throw new AuthorizationException(); //3. Obtain menu id based on role id (sys_role_menu) Integer[] array={}; List<Integer> menuIds= sysRoleMenuDao.findMenuIdsByRoleIds( roleIds.toArray(array)); if(menuIds==null||menuIds.size()==0) throw new AuthorizationException(); //4. Obtain the permission id (sys_menu) based on the menu id List<String> permissions= sysMenuDao.findPermissions(menuIds.toArray(array)); //5. Encapsulate and return the permission identification information Set<String> set=new HashSet<>(); for(String per:permissions){ if(!StringUtils.isEmpty(per)){ set.add(per); } } SimpleAuthorizationInfo info= new SimpleAuthorizationInfo(); info.setStringPermissions(set); return info;//Return to authorization Manager } . . . . }

On the business layer (Service) method requiring authorized access, add the permission ID required to execute this method. Refer to the code @ RequiresPermissions("sys:user:update")
Note: this annotation must be added to the business layer method.
dao layer
Step 1: define a method to find role id based on user id in SysUserRoleDao (if the method already exists, it does not need to be written). The key code is as follows:

List<Integer> findRoleIdsByUserId(Integer id);

Step 2: define the method of finding menu id based on role id in sysrolemeudao. The key codes are as follows:

List<Integer> findMenuIdsByRoleIds( @Param("roleIds")Integer[] roleIds);

Step 3: find the permission id based on the menu id in SysMenuDao. The key codes are as follows:

List<String> findPermissions( @Param("menuIds") Integer[] menuIds);

mapper mapping:
Step 1: define the findRoleIdsByUserId element in SysUserRoleMapper. The key codes are as follows:

<select id="findRoleIdsByUserId" resultType="int"> select role_id from sys_user_roles where user_id=# </select>

Step 2: define the findMenuIdsByRoleIds element in sysrolemenmapper. The key codes are as follows:

<select id="findMenuIdsByRoleIds" resultType="int"> select menu_id from sys_role_menus where role_id in <foreach collection="roleIds" open="(" close=")" separator="," item="item"> # </foreach> </select>

Step 3: define the findPermissions element in SysMenuMapper. The key codes are as follows:

<select id="findPermissions" resultType="string"> select permission <!-- sys:user:update --> from sys_menus where id in <foreach collection="menuIds" open="(" close=")" separator="," item="item"> # </foreach> </select>

V. Shiro extended function application
1Shiro cache configuration
When we perform authorization operations, we will query the user permission information from the database every time. In order to improve the authorization performance, we can query the user permission information and cache it. We can get the data from the cache during the next authorization.
The built-in cache application in Shiro is implemented as follows:
Step 1: configure the cache Bean object in spring shiroconfig (provided by Shiro framework).

@Bean public CacheManager shiroCacheManager(){ return new MemoryConstrainedCacheManager(); }

Note: the name of this cacheManager object cannot be written to cacheManager because an object named cacheManager already exists in the spring container
Step 2: modify the configuration of the securitymanager and inject the cache object into the securitymanager object.

@Bean public SecurityManager securityManager( Realm realm, CacheManager cacheManager) { DefaultWebSecurityManager sManager= new DefaultWebSecurityManager(); sManager.setRealm(realm); sManager.setCacheManager(cacheManager); return sManager; }

Note: for shiro framework, you can also cache the user's permission information with the help of third-party caching products (such as redis)
2Shiro, remember me
Front end js: modify the code in login.html

function doLogin(){ var params={ username:$("#usernameId").val(), password:$("#passwordId").val(), isRememberMe:$("#rememberId").prop("checked"), } var url="user/doLogin"; console.log("params",params); $.post(url,params,function(result){ if(result.state==1){ //Jump to the corresponding page of indexUI location.href="doIndexUI?t="+Math.random(); }else{ $(".login-box-msg").html(result.message); } return false;//Prevent duplicate commit on refresh }); }

backstage
Step 1: in the doLogin method in SysUserController, set the setmemberme method of token based on whether remember me is selected.

@RequestMapping("doLogin") @ResponseBody public JsonResult doLogin( boolean isRememberMe, String username, String password) { //1. Encapsulate user information UsernamePasswordToken token= new UsernamePasswordToken(username, password); if(isRememberMe) { token.setRememberMe(true); } //2. Submit user information Subject subject=SecurityUtils.getSubject(); subject.login(token);//The token will be submitted to the securityManager return new JsonResult("login ok"); }

Step 2: add remember me configuration in the SpringShiroConfig configuration class. The key codes are as follows:

@Bean public RememberMeManager rememberMeManager() { CookieRememberMeManager cManager= new CookieRememberMeManager(); SimpleCookie cookie=new SimpleCookie("rememberMe"); cookie.setMaxAge(7*24*60*60); cManager.setCookie(cookie); return cManager; }

Step 3: modify the configuration of the securityManager in SpringShiroConfig to
securityManager injects the rememberManager object.

@Bean public SecurityManager securityManager( Realm realm,CacheManager cacheManager RememberMeManager rememberManager) { DefaultWebSecurityManager sManager= new DefaultWebSecurityManager(); sManager.setRealm(realm); sManager.setCacheManager(cacheManager); sManager.setRememberMeManager(rememberManager); return sManager; }

Step 4: modify the filtering authentication level of shiro, change / = author to / = users, and view the yellow background.

@Bean public ShiroFilterFactoryBean shiroFilterFactory( SecurityManager securityManager) { ShiroFilterFactoryBean sfBean= new ShiroFilterFactoryBean(); sfBean.setSecurityManager(securityManager); //If there is no authentication request, access the url of this authentication first sfBean.setLoginUrl("/doLoginUI"); //Define map to specify request filtering rules (which resources allow anonymous access and which must be authenticated access) LinkedHashMap<String,String> map= new LinkedHashMap<>(); //Allow anonymous access to static resources: "anon" map.put("/bower_components/**","anon"); map.put("/build/**","anon"); map.put("/dist/**","anon"); map.put("/plugins/**","anon"); map.put("/user/doLogin","anon"); map.put("/doLogout", "logout");//Auto query LoginUrl //Except for anonymous resources, all other resources must be accessed after authentication ("authc") map.put("/**","user");//authc sfBean.setFilterChainDefinitionMap(map); return sfBean; }

3Shiro session duration configuration
The shiro framework is used to realize the authentication operation. If the user logs in successfully, the user information will be written into the session object. The default duration is 30 minutes. If this needs to be configured, please refer to the following configuration:
Step 1: add the session manager configuration in the SpringShiroConfig class. The key codes are as follows:

@Bean public SessionManager sessionManager() { DefaultWebSessionManager sManager= new DefaultWebSessionManager(); sManager.setGlobalSessionTimeout(60*60*1000); return sManager; }

Step 2: in the SpringShiroConfig configuration class, add sessionManager value injection to the security manager securityManager. The key code is as follows:

@Bean public SecurityManager securityManager( Realm realm,CacheManager cacheManager, RememberMeManager rememberManager, SessionManager sessionManager) { DefaultWebSecurityManager sManager= new DefaultWebSecurityManager(); sManager.setRealm(realm); sManager.setCacheManager(cacheManager); sManager.setRememberMeManager(rememberMeManager); sManager.setSessionManager(sessionManager); return sManager; }

4. Realization of related business
4.1 dynamic presentation of user name
Step 1: define a tool class (ShiroUtils) to obtain user login information

package com.cy.pj.common.util; import org.apache.shiro.SecurityUtils; import com.cy.pj.sys.entity.SysUser; public class ShiroUtils { public static String getUsername() { return getUser().getUsername(); } public static SysUser getUser() { return (SysUser) SecurityUtils.getSubject().getPrincipal(); } }

Step 2: modify the doIndexUI method in PageController. The code is as follows:

@RequestMapping("doIndexUI") public String doIndexUI(Model model) { SysUser user=ShiroUtils.getUser(); model.addAttribute("user",user); return "starter"; }

Step 3: get the login user information directly on the page (starter.html) with the expression in thymeleaf

<span id="loginUserId">[[$]]</span>

12 October 2021, 00:23 | Views: 2110

Add new comment

For adding a comment, please log in
or create account

0 comments