Catalog
Preparation
-
About shiro
Three core components: Subject, SecurityManager and Realms- Subject: security operation of current user or current user
- SecurityManage: Security Manager, which manages security operations for all users.
- Realms: the interaction layer (equivalent to dao layer) between Shiro and project data source to complete user authentication (login) and authentication. There is at least one custom realm in the project to inherit authoring realm.
-
Introduce dependency
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <!-- shiro ehcache --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency>
- Create entity class and related business code
The main entity classes are user table, role table, menu (including permissions), userRole and role menu middle table.
Basic steps
- Create MyRealm, complete related authentication and authentication
package com.cm.shiro; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.cm.system.domain.dto.RoleDO; import com.cm.system.domain.dto.UserDO; import com.cm.system.service.MenuService; import com.cm.system.service.RoleService; import com.cm.system.service.UserService; import com.cm.utils.CommomUtil; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; /** * Operation related to authority authentication and authorization * @Author: zlw * @Date: 2019/7/9 15:21 */ @Component("myRealm") public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private MenuService menuService; /** * To grant authorization * This method is called when the target resource or target method is accessed, not immediately after authentication * @param principal role * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //1. Obtain the user information and verify whether the user exists UserDO userDO = (UserDO) principal.getPrimaryPrincipal(); if (null == userDO || null == userDO.getUserId()) { //If the user information is empty, an empty collection is returned return null; } //2. Add role set and permission set to info according to user information List<RoleDO> roles = roleService.findRolesByUserId(userDO.getUserId()); roles.forEach(roleDO -> authorizationInfo.addRole(roleDO.getRoleName())); Set<String> perms = menuService.listPerms(userDO.getUserId());//Query all permissions of the user according to the id authorizationInfo.addStringPermissions(perms); return authorizationInfo; } /** * Landing verification * Execute subject.login(token) in the login interface; the method will be called * @param authenticationToken Authenticated user information * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //0. Convert token to UsernamePasswordToken to get user name UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); // Solve the problem of SQL injection if (StringUtils.isBlank(username) || CommomUtil.sqlInjCheck(username)) { throw new UnknownAccountException(); } //1. Query the user information from the database through username, and judge the account status. EntityWrapper<UserDO> wrapper = new EntityWrapper<>(); wrapper.eq("username", username); UserDO userDO = userService.selectOne(wrapper); if (null == userDO) { //Account does not exist throw new UnknownAccountException(); } //Judge account status, can be omitted if (userDO.getStatus() == 0) { //Account locked throw new LockedAccountException(); } //2. Generate the attribute of adding salt in the authenticationInfo object todo user object, and return jsonIgnore ByteSource.Util.bytes("") to the front end. SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userDO,userDO.getPassword(), ByteSource.Util.bytes("zlwcm"), getName()); return authenticationInfo; } }
- Related configuration of shiro
package com.cm.shiro; import com.cm.system.service.MenuService; import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; /** * Shiro To configure */ @Slf4j @Configuration public class ShiroConfig { @Autowired private RedisProperties redisProperties; @Autowired private MenuService menuService; @Bean public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); bean.setSuccessUrl("/Successful login url"); bean.setLoginUrl("/login"); // Permission Filter Map<String, Filter> filterMap = new LinkedHashMap<>(1); filterMap.put("authc", new CaptchaFormAuthenticationFilter()); bean.setFilters(filterMap); //todo configuration access LinkedHashMap<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap(); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/course/list/**", "authc"); bean.setFilterChainDefinitionMap(filterChainDefinitionMap); return bean; } /** * Certificate matcher (because our password verification was handed over to Shiro's SimpleAuthenticationInfo for processing) * @return HashedCredentialsMatcher */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // Using MD5 hash algorithm hashedCredentialsMatcher.setHashAlgorithmName("md5"); // Hash times hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher; } /** * Set the password encryption mode to be consistent with the password encryption mode when the user registers. See tool class Md5Utils for specific operation * Otherwise, incorrect password exception will be reported during verification. * @return */ @Bean public MyRealm authShiroRealm() { MyRealm authShiroRealm = new MyRealm(); authShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return authShiroRealm; } /** * Security Manager * @return */ @Bean public org.apache.shiro.mgt.SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(authShiroRealm()); // Custom session management, using Redis securityManager.setSessionManager(sessionManager()); // Customized cache implementation, using Redis securityManager.setCacheManager(cacheManager()); return securityManager; } /** * Customizing session manager * @return sessionManager */ @Bean public SessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /** * To configure shiro redisManager, the Shiro redis open source plug-in is used. */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(redisProperties.getHost()); redisManager.setPort(redisProperties.getPort()); redisManager.setPassword(redisProperties.getPassword()); redisManager.setDatabase(redisProperties.getDatabase()); return redisManager; } /** * cacheManager Cache redis implementation */ @Bean public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * RedisSessionDAO shiro sessionDao Layer is implemented through redis */ @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); redisSessionDAO.setKeyPrefix("shiro:user:"); return redisSessionDAO; } /** * Enable Shiro aop annotation support * Use proxy mode; therefore, code support needs to be turned on. * @param securityManager securityManager * @return advisor */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
- Login to call LoginController
package com.cm.system.controller; import com.cm.aop.SysLog; import com.cm.utils.R; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Login related operations */ @RestController @Slf4j public class LoginController { @SysLog("Land") @GetMapping("login") public R login(@RequestParam("username")String username,@RequestParam("password") String password) { Subject subject = SecurityUtils.getSubject(); // password = MD5Utils.encrypt(password); UsernamePasswordToken token = new UsernamePasswordToken(username, password); String failMsg = ""; try { subject.login(token); }catch (UnknownAccountException e) { failMsg = "user does not exist"; } catch (IncorrectCredentialsException e) { failMsg = "Wrong password!"; } catch (LockedAccountException e) { failMsg = "Login failed, the user has been frozen"; } catch (Exception e) { log.info("System internal exception!!{}", e); failMsg = "System internal exception!!"; } return failMsg == "" ? R.ok() : R.error(failMsg); } }
- Related tools
package com.cm.utils; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MD5Utils { private static final String SALT = "zlwcm"; private static final String ALGORITH_NAME = "md5"; private static final int HASH_ITERATIONS = 2; private static final Logger LOGGER = LoggerFactory.getLogger(MD5Utils.class); private MD5Utils() { super(); } public static String encrypt(String pswd) { String newPassword = new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(SALT), HASH_ITERATIONS).toHex(); return newPassword; } }
Common abnormality
- The database password is inconsistent with the user password: the encryption method is inconsistent, the user password is entered or the transmission conversion is wrong
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=false] did not match the expected credentials.