This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together

Having written two articles on Shiro and Spring, it is necessary to integrate Shiro and Spring Boot with the current trend of Developing the Spring Boot framework in Java. When I use Spring Boot, the most obvious feeling is that the XML configuration of Spring has been replaced by Java beans. The overall view of Java code is much purer.

First, look at dependencies

<! --shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> The < version > 1.4.0 < / version > < / dependency > <! --redis--> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.2.3</version> </dependency>Copy the code

The first dependency is spring and Shiro’s dependency, and the version is not new compared to the current version. The second is a plugin that uses Redis to cache shiro’s authorization information.

Then there is shiro’s configuration process. I like to put the contents of the security framework in a package so that it feels clearer

The first new step is a ShiroConfig. The Configuration annotation marks it as a Configuration class. Then we define a few variables

Variables here are mainly associated with redis configuration, such as IP, secret, port, expiration time. Values can be written to the configuration file YML or assigned directly.

This is the configuration (XML version) of Shiro’s official website integrating Spring. Basically we can see that this is defining a custom shiroFilter

Configure some parameters, mainly login route, login successful route, unauthorized route, custom filter chain, and custom filter,

There is also the securityManager securityManager

The following is the spring Boot version of the Java bean format. We can see that most of the arguments are just in a different form. We first instantiated a filter factory and added two custom filters to handle cross-domain and login interception. Then we declare a map, in which key represents the route used in our system request, and value represents the permission required for this route. Here anon stands for visitor privileges, which can be seen in previous blog posts. Static resources such as Swagger interface documents and login interfaces are allowed.

/** * @description filter * @author zhou * @created 2019/3/13 15:47 * @param * @return */ @bean public ShiroFilterFactoryBean shrioFilter(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new  ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String,Filter> filterMap = new LinkedHashMap<String,Filter>(); filterMap.put("cros",crosFilter()); filterMap.put("login",logInterceptor()); shiroFilterFactoryBean.setFilters(filterMap); // Interceptor Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); filterChainDefinitionMap.put("/swagger-ui.html", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/v2/**", "anon"); filterChainDefinitionMap.put("/swagger-resources/**", "anon"); filterChainDefinitionMap.put("/back/admin/login/**","anon"); filterChainDefinitionMap.put("/back/admin/sendCode/**","anon"); filterChainDefinitionMap.put("/back/admin/forgetPassword/**","anon"); filterChainDefinitionMap.put("/shop/**","anon"); //filterChainDefinitionMap.put("/back/**","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }Copy the code

And then the important thing is that the security manager is where the authorization logic unfolds. There are three main things that are configured here: the first is the implementation of authorization and authentication, the second is session management, and the third is cache management

/** * @description SecurityManager * @author zhou * @created 2019/3/13 15:42 * @param * @return */ @bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); securityManager.setSessionManager(sessionManager()); securityManager.setCacheManager(redisCacheManager()); return securityManager; }Copy the code

The implementation of user login authorization and authentication, and the encryption policy. Let’s look at the code implementation

Realm This is no different from the previous XML version, we create a MyShirorealm. class and then inherit AuthorizingRealm. The main thing here is to override the authorization and validation methods in the parent class. The internal business logic may vary from system to system, but the overall idea is the same.

The first method is authorization. The main thing to do is to call the internal business logic using the user name to get your set of roles and permissions, and then store your roles and permissions in the SimpleAuthorizationInfo object, so that subsequent method calls can be annotated to determine the permissions. You can generally store String roles and permissions.

The second is login authentication. This method is mainly to obtain the user from your database through the user name, and then do some disabled and expired verification for the user, and then return the user name and password encapsulation, and let Shiro do the following password matching.

/** * @description grants roles and permissions to the currently logged user * @author zhou * @created 2019/4/30 15:03 * @param * @return */ @override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection PrincipalCollection) {// Obtain the user name String name = (String) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); / / query the user's role Set < String > roles. = roleService getRoleNameByUserName (name); authorizationInfo.setRoles(roles); return authorizationInfo; } /** * @description identifies the current user * @author zhou * @created 2019/4/30 16:28 * @param * @return */ @override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { / / get the user information String name = (String) authenticationToken. GetPrincipal (); TbUser = usermapper.getUserByName (name); If (null == tbUser){throw new UnknownAccountException() does not exist; }else if(tbuser.getDeleted ()){throw new LockedAccountException(); } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(tbUser.getName(), tbUser.getPassword(), ByteSource.Util.bytes(tbUser.getSalt()),getName()); return authenticationInfo; }Copy the code

The password utility class also needs to be configured in a custom RealmCopy the code
/** * @description custom Realm * @author Zhou * @created 2019/3/13 15:43 * @param * @return */ @bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; }Copy the code

The specific password class is as follows. Here, a hash password tool class is used to define the encryption algorithm and encryption times, and the password is salted. When our previous authentication method returns, Shiro will handle password matching based on the password matcher configured in your Realm

/** * @description password comparator * @author zhou * @created 2018/12/27 9:34 * @param * @return */ @bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashIterations(2); hashedCredentialsMatcher.setHashAlgorithmName("md5"); return hashedCredentialsMatcher; }Copy the code

/** * @author zhou * @created 2019/3/14 20:23 * @param * @return */ public TbUser encryptPassword(TbUser tbUser){ if(null == tbUser.getSalt() || "".equals(tbUser.getSalt())){ tbUser.setSalt(UUID.randomUUID().toString().replace("-","")); } String password = new SimpleHash(hashedCredentialsMatcher.getHashAlgorithmName(),tbUser.getPassword(), ByteSource.Util.bytes(tbUser.getSalt()),hashedCredentialsMatcher.getHashIterations()).toHex(); tbUser.setPassword(password); return tbUser; } /** * @description encryption * @author zhou * @created 2019/5/13 15:39 * @param password password * @param salt * @return */ public String getNewPassword(String password,String salt){ String newPassword = new SimpleHash(hashedCredentialsMatcher.getHashAlgorithmName(),password, ByteSource.Util.bytes(salt),hashedCredentialsMatcher.getHashIterations()).toHex(); return newPassword; }Copy the code

User authorization and login authentication as described above. You need to get the body of the current session from the login interface, which is the first line of code. Then you need to wrap your username and password into a Shiro approved UsernamePasswordToken object. This object is simply a credential. Then call login with the credentials as arguments in the principal, which will then call the override method in your custom realm.

// getSubject currentUser = securityutils.getsubject (); UsernamePasswordToken token = new UsernamePasswordToken(loginparam.getName (), loginparam.getPassword ()); currentUser.login(token);Copy the code

Let’s talk about two other security manager configurations: sessionManager and cacheManager(session management and cache management). This session management can have different storage vehicles such as memory or Redis.

Here we take a look at the definition and description of the session manager in the document, which roughly means that the manager is used to add, delete, modify and check the session information, and the information is used to call the program after the first login. Users can choose the database (mysql) or Nosql(Redis, etc.) to store the information.

I use Redis for the data access layer of session management. Because Shiro’s default session is based on server memory, which is not suitable for production environment, I use Redis for persistence. Here, a RedisManager is configured to be shared by session management and cache management. The common configuration is relatively simple: redis serialization, port and so on.

/** * @description Shiro - Redis open source plugin * @author Zhou * @created 2019/3/22 17:01 * @param * @return */ @bean public RedisManager redisManager(){ RedisManager redisManager = new RedisManager(); redisManager.setHost(host+":"+port); redisManager.setPassword(password); redisManager.setTimeout(timeout); return redisManager; } /** * @description sessionDao * @author zhou * @created 2019/3/22 17:03 * @param * @return */ @bean public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setKeySerializer(new StringSerializer()); redisSessionDAO.setValueSerializer(new ObjectSerializer()); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; }Copy the code

We then look at the source code for the RedisSessionDAO to see exactly what information it caches. Caches the entire session with the sessionId as the key.Copy the code

And then the cache manager, I was a little vague at first if session management is already redis, why a cache manager? According to the documentation, session management is just the session that handles user logins and interactions, while the cache manager caches all the data shiro needs to cache. Also, when you configure cacheManager, sessionDAO will call your cacheManager during persistence. This is my understanding of the documentCopy the code

The next step is to add an activation annotation configuration so that authentication annotations can be added to the method

/** * @description annotation enabled * @author zhou * @created 2018/12/27 9:35 * @param * @return */ @bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }Copy the code

At this point, the general framework of Spring Boot and Shiro is clear, as well as some details of annotation usage, logout, and source code, which will not be extended due to space constraints. Hopefully it will help you integrate Shiro with Spring Boot