My official account is MarkerHub and my website is Markerhub.com

For more selected articles, please click: Java Notes Complete.md

Small Hub read:

Have source code tutorial, not the classmate download source code, according to the tutorial to learn about ha ~


Author: Sans_

juejin.im/post/5d087d605188256de9779e64

A. Instructions

Shiro is a security framework, which is mainly used for authentication, authorization, encryption, and user session management in projects. Although Shiro is not as rich as SpringSecurity, it is lightweight and simple, and Shiro is also competent for business requirements in projects.

Ii. Project environment

  • MyBatis-Plus version: 3.1.0

  • SpringBoot version: 2.1.5

  • JDK version: 1.8

  • Shiro version: 1.4

  • Shiro-redis plugin version: 3.1.0

Data table (SQL file in the project): The password of the test number in the database is encrypted, and the password is 123456

Maven has the following dependencies:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId>  </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <! -- Lombok plugin --> <dependency> <groupId>org.projectlombok</groupId> <artifactId> <optional>true</optional> </dependency> <! -- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <! MybatisPlus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> The < version > 3.1.0 < / version > < / dependency > <! Alibaba </groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> <! --> <dependency> <groupId>org.apache. Shiro </groupId> <artifactId> Shiro -spring</artifactId> The < version > 1.4.0 < / version > < / dependency > <! --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> The < version > 3.1.0 < / version > < / dependency > <! Mons </groupId> <artifactId> Commons -lang3</artifactId> The < version > 3.5 < / version > < / dependency > < / dependencies >Copy the code

The configuration is as follows:

Datasource: driver-class-name: com.mysql.cj.jdbc.driver url: jdbc:mysql://localhost:3306/my_shiro? serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false username: Root password: root type: com. Alibaba. Druid. Pool. DruidDataSource # Redis data Redis: host: localhost port: 6379 timeout: 6000 password: 123456 jedis: pool: max-active: 1000 # Maximum number of connections in the pool (negative value indicates no limit) max-wait: Min-idle: 10 # Maximum idle connections in the connection pool min-idle: 5 # Minimum idle connections in the connection pool mybatis-plus Mapper-locations: # XML scan, multiple directories separated by commas or semicolons (;) Classpath :mapper/*.xml # primary key type AUTO:" database ID increment "INPUT:" user INPUT ID",ID_WORKER:" globally unique ID (number type unique ID)", UUID:" globally unique ID UUID"; Id -type: auto # Field policy IGNORED:" IGNORED judgment "NOT_NULL:" Non-null judgment ") NOT_EMPTY:" Non-null judgment" field-strategy: NOT_EMPTY # Database type db-type: MYSQL Configuration: # enable automatic camel naming rule mapping from database column names to Java attribute camel names like map-underscore to camel-case: Call-setters-on-nulls: true # If the query result contains a null column, MyBatis will not map this column when mapping. org.apache.ibatis.logging.stdout.StdOutImplCopy the code

Two. Write the project base class

User entities, Dao,Service, etc. are omitted here, please refer to the source code

Write an Exception class to handle Shiro permission interception exceptions

Create the SHA256Util encryption tool

Creating the Spring tool

/** * @description Spring context utility class * @author Sans * @createTime 2019/6/17 13:40 */ @Component public class SpringUtil implements ApplicationContextAware { private static ApplicationContext context; /** * Spring determines if the bean is a subclass of ApplicationContextAware after it is initialized. If it is, the setApplicationContext() method passes in the ApplicationContext from the container as an argument * @Author Sans * @CreateTime 2019/6/17 16:58 */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public static <T> T getBean(Class<T> beanClass) { return context.getBean(beanClass); }}Copy the code

Create the Shiro tool

/** * @description Shiro class * @author Sans * @createTime 2019/6/15 16:11 */ public class ShiroUtils {/** private constructor **/ private ShiroUtils(){} private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class); /** * Obtain the current user Session * @author Sans * @createTime 2019/6/17 17:03 * @return SysUserEntity user information */ public static Session getSession() { return SecurityUtils.getSubject().getSession(); } /** * @author Sans @createTime 2019/6/17 17:23 */ public static void logout() { SecurityUtils.getSubject().logout(); } /** * Get current user info * @author Sans * @createTime 2019/6/17 17:03 * @return SysUserEntity user info */ public static SysUserEntity getUserInfo() { return (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); } /** * delete user cache information * @author Sans * @createtime 2019/6/17 13:57 * @param username username * @param isRemoveSession Session * @return void */ public static void deleteCache(String username, Boolean isRemoveSession){// Get Session from cache Session = null; Collection<Session> sessions = redisSessionDAO.getActiveSessions(); SysUserEntity sysUserEntity; Object attribute = null; for(Session sessionInfo : Sessions){// Iterate through the Session and find the Session attribute = corresponding to the user name sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); if (attribute == null) { continue; } sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal(); if (sysUserEntity == null) { continue; } if (Objects.equals(sysUserEntity.getUsername(), username)) { session=sessionInfo; } } if (session == null||attribute == null) { return; Session if (isRemoveSession) {redissessiondao.delete (session); } // Delete the Cache. When limited access interface reauthorization DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); Authenticator authc = securityManager.getAuthenticator(); ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute); }}Copy the code

Create Shiro’s SessionId generator

Write Shiro core classes

Create a Realm for authorization and authentication

/** * @description Shiro Permission matching account password matching * @author Sans * @createTime 2019/6/15 11:27 */ public class ShiroRealm extends AuthorizingRealm { @Autowired private SysUserService sysUserService; @Autowired private SysRoleService sysRoleService; @Autowired private SysMenuService sysMenuService; * @author Sans * @createTime 2019/6/12 11:44 */ @override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal(); Long userId = sysuserEntity.getUserId (); Set<String> rolesSet = new HashSet<>(); Set<String> permsSet = new HashSet<>(); / / query roles and permissions (here according to the business query) List < SysRoleEntity > sysRoleEntityList = sysRoleService. SelectSysRoleByUserId (userId); for (SysRoleEntity sysRoleEntity:sysRoleEntityList) { rolesSet.add(sysRoleEntity.getRoleName()); List<SysMenuEntity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId()); for (SysMenuEntity sysMenuEntity :sysMenuEntityList) { permsSet.add(sysMenuEntity.getPerms()); }} / / check the permissions and roles respectively into the authorizationInfo authorizationInfo. SetStringPermissions (permsSet); authorizationInfo.setRoles(rolesSet); return authorizationInfo; } /** * Authentication * @author Sans * @createTime 2019/6/12 12:36 */ @override protected AuthenticationInfo DoGetAuthenticationInfo (AuthenticationToken AuthenticationToken) throws AuthenticationException {// Obtains the account entered by the user. String username = (String) authenticationToken.getPrincipal(); // Find the User object in the database by username. SysUserEntity user = Shiro does not execute this method twice for 2 minutes sysUserService.selectUserByName(username); If (user == null) {throw new AuthenticationException(); } / / judge whether account be frozen if the user. The getState () = = null | | user. The getState () equals (" PROHIBIT ")) {throw new LockedAccountException (); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, // user.getPassword(), Bytesource.util.bytes (user.getsalt ()), // set the salt value getName()); Shiroutils. deleteCache(username,true); shiroutils. deleteCache(username,true); return authenticationInfo; }}Copy the code

Create our SessionManager class

Create the ShiroConfig configuration class

/** * @description Shiro config class * @author Sans * @createTime 2019/6/10 17:42 */ @configuration public class ShiroConfig { private final String CACHE_KEY = "shiro:cache:"; private final String SESSION_KEY = "shiro:session:"; private final int EXPIRE = 1800; @value ("${spring.redis. Host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.timeout}") private int timeout; @Value("${spring.redis.password}") private String password; /** * enable shiro-AOP annotation support * @attention uses proxy mode so need to enable code support * @author Sans * @createTime 2019/6/128:38 */ @bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * Shiro base configuration * @author Sans * @createTime 2019/6/128:42 */ @bean public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); / / note that filter configuration order cannot reverse / / configuration: the link will not be intercepted filterChainDefinitionMap. Put ("/static / * * ", "-anon"); filterChainDefinitionMap.put("/userLogin/**", "anon"); filterChainDefinitionMap.put("/**", "authc"); / / configure shiro default login interface address, before and after the end of the separation of the login interface jump should be controlled by the front-end routing, the background just return the json data shiroFilterFactoryBean. SetLoginUrl ("/userLogin/unauth "); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * SecurityManager * @author Sans * @createtime 2019/6/12 10:34 */ @bean public SecurityManager SecurityManager () { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); / / a custom securityManager Ssession management. SetSessionManager (our sessionManager ()); / / a custom Cache implementation securityManager. SetCacheManager (cacheManager ()); // Custom Realm validation securityManager.setrealm (shiroRealm()); return securityManager; } /** * authentication * @author Sans * @createTime 2019/6/12 10:37 */ @bean public ShiroRealm ShiroRealm () {ShiroRealm shiroRealm = new ShiroRealm(); shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return shiroRealm; } /** * The credentialmatcher * passes the password verification to Shiro's SimpleAuthenticationInfo for processing, where the matching configuration is done * @author Sans * @createTime 2019/6/12 10:48 */ @bean  public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher(); // Hash algorithm: this uses SHA256 algorithm; shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME); // The number of hashes, such as hashes twice, equals md5(MD5 ("")); shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS); return shaCredentialsMatcher; } @author Sans * @createTime 2019/6/12 11:06 */ @bean public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port); redisManager.setTimeout(timeout); redisManager.setPassword(password); return redisManager; } /** * Configure the Cache manager * to store permissions and role ids to Redis * @attention uses shiro- Redis open source plugin * @author Sans * @createTime 2019/6/12 12:37 */ @Bean public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); redisCacheManager.setKeyPrefix(CACHE_KEY); / / cache configuration requirements on the inside of the session entity class must have a id identifies redisCacheManager. SetPrincipalIdFieldName (" userId "); return redisCacheManager; } /** * SessionID generator * @author Sans * @createTime 2019/6/12 13:12 */ @bean public ShiroSessionIdGenerator sessionIdGenerator(){ return new ShiroSessionIdGenerator(); } /** * RedisSessionDAO * @attention * @author Sans * @createTime 2019/6/12 13:44 */ @bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); redisSessionDAO.setSessionIdGenerator(sessionIdGenerator()); redisSessionDAO.setKeyPrefix(SESSION_KEY); redisSessionDAO.setExpire(expire); return redisSessionDAO; } /** * @author Sans * @createTime 2019/6/12 14:25 */ @bean public SessionManager SessionManager () { ShiroSessionManager shiroSessionManager = new ShiroSessionManager(); shiroSessionManager.setSessionDAO(redisSessionDAO()); return shiroSessionManager; }}Copy the code

4. Realization of permission control

Shiro can use code or annotations to control permissions. Usually we use annotations to control permissions, which is not only simple and convenient, but also more flexible. There are five Shiro notes:

For example, for RequiresPermissions AND RequiresRoles, you can have multiple roles AND permissions. The default logic is AND, which allows you to access a method if you have both. You can set it to OR as a parameter in annotations

The sample

Use order: Shiro annotations exist in order. When multiple annotations are on a method, they are checked one by one until they all pass. The default order of interception is: RequiresRoles->RequiresPermissions->RequiresAuthentication-> RequiresUser->RequiresGuest

The sample

Create the UserRoleController role to intercept the test class

** * @description Role test * @author Sans * @createTime 2019/6/19 11:38 */ @restController @requestMapping ("/role") public class UserRoleController { @Autowired private SysUserService sysUserService; @Autowired private SysRoleService sysRoleService; @Autowired private SysMenuService sysMenuService; @Autowired private SysRoleMenuService sysRoleMenuService; /** * Administrator role test interface * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getAdminInfo") @RequiresRoles("ADMIN") public Map<String,Object> getAdminInfo(){ Map<String,Object> map = new HashMap<>(); map.put("code",200); Map. put(" MSG "," this is the interface that only the administrator can access "); return map; } /** * User role test interface * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getUserInfo") @RequiresRoles("USER") public Map<String,Object> getUserInfo(){ Map<String,Object> map =  new HashMap<>(); map.put("code",200); Map. put(" MSG "," this is the interface that only user roles can access "); return map; } /** * Role test interface * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getRoleInfo") @RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR) @RequiresUser public Map<String,Object> getRoleInfo(){ Map<String,Object> map = new HashMap<>(); map.put("code",200); Map. put(" MSG "," here is the interface that can be accessed by any ADMIN or USER role "); return map; } /** * logout (test logout) * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object @RequestMapping("/getLogout") @RequiresUser public Map<String,Object> getLogout(){ ShiroUtils.logout(); Map<String,Object> map = new HashMap<>(); map.put("code",200); The map. The put (" MSG ", "logout"); return map; }}Copy the code

Create the UserMenuController permission blocking test class

/** * @description permission test * @author Sans * @createTime 2019/6/19 11:38 */ @restController @requestMapping ("/menu") public class UserMenuController { @Autowired private SysUserService sysUserService; @Autowired private SysRoleService sysRoleService; @Autowired private SysMenuService sysMenuService; @Autowired private SysRoleMenuService sysRoleMenuService; @author Sans * @createTime 2019/6/19 10:36 * @return Map<String,Object> Result */ @RequestMapping("/getUserInfoList") @RequiresPermissions("sys:user:info") public Map<String,Object> getUserInfoList(){ Map<String,Object> map = new HashMap<>(); List<SysUserEntity> sysUserEntityList = sysUserService.list(); map.put("sysUserEntityList",sysUserEntityList); return map; } @author Sans * @createTime 2019/6/19 10:37 * @return Map<String,Object> Result */ @RequestMapping("/getRoleInfoList") @RequiresPermissions("sys:role:info") public Map<String,Object> getRoleInfoList(){ Map<String,Object> map = new HashMap<>(); List<SysRoleEntity> sysRoleEntityList = sysRoleService.list(); map.put("sysRoleEntityList",sysRoleEntityList); return map; } /** * Get permission information set * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Result */ @RequestMapping("/getMenuInfoList") @RequiresPermissions("sys:menu:info") public Map<String,Object> getMenuInfoList(){ Map<String,Object> map = new HashMap<>(); List<SysMenuEntity> sysMenuEntityList = sysMenuService.list(); map.put("sysMenuEntityList",sysMenuEntityList); return map; } @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getInfoAll") @RequiresPermissions("sys:info:all") public Map<String,Object> getInfoAll(){ Map<String,Object> map = new HashMap<>(); List<SysUserEntity> sysUserEntityList = sysUserService.list(); map.put("sysUserEntityList",sysUserEntityList); List<SysRoleEntity> sysRoleEntityList = sysRoleService.list(); map.put("sysRoleEntityList",sysRoleEntityList); List<SysMenuEntity> sysMenuEntityList = sysMenuService.list(); map.put("sysMenuEntityList",sysMenuEntityList); return map; } /** * Add administrator role permission (test dynamic permission update) * @author Sans * @createTime 2019/6/19 10:39 * @param username user ID * @return */ @requestMapping ("/addMenu") public Map<String,Object> addMenu(){// Add permission for the administrator role SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity(); sysRoleMenuEntity.setMenuId(4L); sysRoleMenuEntity.setRoleId(1L); sysRoleMenuService.save(sysRoleMenuEntity); // Clear cache String username = "admin"; ShiroUtils.deleteCache(username,false); Map<String,Object> map = new HashMap<>(); map.put("code",200); Map. put(" MSG "," permission added successfully "); return map; }}Copy the code

Create the UserLoginController login class

@author Sans * @createTime 2019/6/17 15:21 */ @restController @requestMapping ("/userLogin") public class UserLoginController { @Autowired private SysUserService sysUserService; /** * @author Sans * @createTime 2019/6/20 9:21 */ @requestMapping ("/login") public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){ Map<String,Object> map = new HashMap<>(); Try {// Verify identity and login Subject Subject = securityutils.getSubject (); UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword()); // Verify successful login operation subject.login(token); }catch (IncorrectCredentialsException e) { map.put("code",500); Map. put(" MSG "," user does not exist or password error "); return map; } catch (LockedAccountException e) { map.put("code",500); Map. put(" MSG "," login failed, the user has been frozen "); return map; } catch (AuthenticationException e) { map.put("code",500); Map. put(" MSG "," user does not exist "); return map; } catch (Exception e) { map.put("code",500); Map. put(" MSG "," unknown exception "); return map; } map.put("code",0); Map. put(" MSG "," login succeeded "); map.put("token",ShiroUtils.getSession().getId().toString()); return map; } @author Sans @createTime 2019/6/20 9:22 */ @requestMapping ("/unauth") public Map<String,Object> unauth(){ Map<String,Object> map = new HashMap<>(); map.put("code",500); Map. put(" MSG "," not logged in "); return map; }}Copy the code

Five. POSTMAN test

After a successful login, the Redis TOKEN will be returned, because it is a single sign-on. If you log in again, the Redis TOKEN will be returned, and the previous TOKEN will be invalid

When accessing the interface for the first time, Shiro will directly fetch the permission from the cache. Note that the request header must be set when accessing the interface.

ADMIN does not have the permission sys:info:all, so it cannot access the getInfoAll interface. After dynamically assigning the permission, Shiro will clear the cache. When accessing the interface, Shiro will perform the authorization method again, and then put the permission and role data into the cache again

Access add permission test interface, because it is a test, I add permission user ADMIN dead in the inside, after adding permission, call the tool class to clear the cache, we can find that Redis has no cache

Access the getInfoAll interface again, because there is no data in the cache, Shiro will reauthorize the query and the interception passes

Six. Project source code

Gitee.com/liselotte/s…

Github.com/xuyulong201…


(after)

Recommended reading

Java Notes Complete.md

Great, this Java site, everything! https://markerhub.com

The UP master of this B station, speaks Java really good!

Too great! The latest edition of Java programming ideas can be viewed online!