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
The data table name | Chinese name of the table | descr |
---|---|---|
sys_user | System user table | Based on table |
sys_menu | Permissions on the table | Based on table |
sys_role | Character sheet | Based on table |
sys_role_menu | Table of roles and permission relationships | In the middle of table |
sys_user_role | User – role relationship table | In the middle of table |
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>
<! AOP dependencies must be added, otherwise permission interception validation will not take effect -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<! -- Lombok plugin -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<! -- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<! -- mybatisPlus Core library -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<! Ali database connection pool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<! -- Shiro core dependencies -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<! -- Shiro-redis plugin -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
<! -- StringUtilS tool -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
Copy the code
The configuration is as follows:
# config port
server:
port: 8764
spring:
# configure data source
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 source
redis:
host: localhost
port: 6379
timeout: 6000
password: 123456
jedis:
pool:
max-active: 1000 # maximum number of connections in the pool (use negative values to indicate no limit)
max-wait: - 1 Maximum connection pool blocking wait time (negative value indicates no limit)
max-idle: 10 The maximum number of free connections in the connection pool
min-idle: 5 Minimum free connection in connection pool
# Mybatis -plus related configuration
mybatis-plus:
# XML scan, multiple directories separated by commas or semicolons (to tell Mapper where the corresponding XML file is)
mapper-locations: classpath:mapper/*.xml
The following configurations have default values
global-config:
db-config:
# 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:" ignore judgment "NOT_NULL:" non-null judgment ") NOT_EMPTY:" Non-null judgment"
field-strategy: NOT_EMPTY
# database type
db-type: MYSQL
configuration:
Enable automatic camel name mapping: a similar mapping from database column names to Java attribute camel names
map-underscore-to-camel-case: true
True: null is returned if the map is null. False: hidden if the map is null
call-setters-on-nulls: true
This configuration prints out the executed SQL for use during development or testing
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Copy 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
/ * * *@DescriptionCustom exception *@Author Sans
* @CreateTime2019/6/15 22:56 * /
@ControllerAdvice
public class MyShiroException {
/** * Handle Shiro permission blocking exception * If return JSON data format please add@ResponseBodyNote *@Author Sans
* @CreateTime2019/6/15 "*@ReturnMap<Object> returns the result set */
@ResponseBody
@ExceptionHandler(value = AuthorizationException.class)
public Map<String,Object> defaultErrorHandler(a){
Map<String,Object> map = new HashMap<>();
map.put("403"."Insufficient authority");
returnmap; }}Copy the code
Create the SHA256Util encryption tool
/ * * *@DescriptionSha-256 Encryption tool *@Author Sans
* @CreateTime2019/6/12 9:27 * /
public class SHA256Util {
/** Private constructor **/
private SHA256Util(a){};
/** Encryption algorithm **/
public final static String HASH_ALGORITHM_NAME = "SHA-256";
/** loop number **/
public final static int HASH_ITERATIONS = 15;
/** Perform encryption - use SHA256 and salt encryption **/
public static String sha256(String password, String salt) {
return newSimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString(); }}Copy the code
Creating the Spring tool
/ * * *@DescriptionSpring context utility classes *@Author Sans
* @CreateTime2019/6/17 "* /
@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
* @CreateTime2019/6/17 16:58 * /
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
/** * returns the specified Bean * by Name@Author Sans
* @CreateTime2019/6/17 16:03 * /
public static <T> T getBean(Class<T> beanClass) {
returncontext.getBean(beanClass); }}Copy the code
Create the Shiro tool
/ * * *@DescriptionShiro utility classes *@Author Sans
* @CreateTime2019/6/15 tightly with * /
public class ShiroUtils {
/** Private constructor **/
private ShiroUtils(a){}
private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
/** * Get the current user Session *@Author Sans
* @CreateTime 2019/6/17 17:03
* @ReturnSysUserEntity User information */
public static Session getSession(a) {
return SecurityUtils.getSubject().getSession();
}
/** * User logout *@Author Sans
* @CreateTime2019/6/17 17:23 * /
public static void logout(a) {
SecurityUtils.getSubject().logout();
}
/** * Get the current user information *@Author Sans
* @CreateTime 2019/6/17 17:03
* @ReturnSysUserEntity User information */
public static SysUserEntity getUserInfo(a) {
return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
}
/** * Delete user cache information *@Author Sans
* @CreateTime2019/6/17 and * appear@ParamUsername username *@ParamIsRemoveSession Whether to delete the Session *@Return void
*/
public static void deleteCache(String username, boolean isRemoveSession){
// Get Session from cache
Session session = null;
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
SysUserEntity sysUserEntity;
Object attribute = null;
for(Session sessionInfo : sessions){
// Traverses the Session and finds the Session corresponding to the user name
attribute = 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;
break; }}if (session == null||attribute == null) {
return;
}
/ / delete the session
if (isRemoveSession) {
redisSessionDAO.delete(session);
}
// Delete the Cache and reauthorize access to restricted interfacesDefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); Authenticator authc = securityManager.getAuthenticator(); ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute); }}Copy the code
Create Shiro’s SessionId generator
/ * * *@DescriptionCustom SessionId generator *@Author Sans
* @CreateTimeTruly 2019/6/11 * /
public class ShiroSessionIdGenerator implements SessionIdGenerator {
/** * implement SessionId generation *@Author Sans
* @CreateTime2019/6/11 11:54 * /
@Override
public Serializable generateId(Session session) {
Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
return String.format("login_token_%s", sessionId); }}Copy the code
Write Shiro core classes
Create a Realm for authorization and authentication
/ * * *@DescriptionShiro permission match and account password match *@Author Sans
* @CreateTime2019/6/15 at * /
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
/** * Authorization * Shiro will check the user's permission in the cache. If the user does not find the data, Shiro will execute this method to check the permission and put it in the cache *@Author Sans
* @CreateTime2019/6/12 11:44 * /
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();
// Get the user ID
Long userId =sysUserEntity.getUserId();
// This is where authorization and processing can take place
Set<String> rolesSet = new HashSet<>();
Set<String> permsSet = new HashSet<>();
// Query roles and permissions.
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()); }}// Pass the found permissions and roles into 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 {
// Get the user's input account.
String username = (String) authenticationToken.getPrincipal();
// Use username to find the User object in the database
// In a real project, this can be done according to the actual situation. If not,Shiro has its own time interval mechanism and will not execute this method again for 2 minutes
SysUserEntity user = sysUserService.selectUserByName(username);
// Check whether the account exists
if (user == null) {
throw new AuthenticationException();
}
// Check whether the account is frozen
if (user.getState()==null||user.getState().equals("PROHIBIT")) {throw new LockedAccountException();
}
// Verify
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, / / user name
user.getPassword(), / / password
ByteSource.Util.bytes(user.getSalt()), // Set the salt value
getName()
);
// Start kicking successfully (clear cache and Session)
ShiroUtils.deleteCache(username,true);
returnauthenticationInfo; }}Copy the code
Create our SessionManager class
/ * * *@DescriptionObtain a Token * by custom@Author Sans
* @CreateTime2019/6/13 8:34 * /
public class ShiroSessionManager extends DefaultWebSessionManager {
// Define constants
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
// Override the constructor
public ShiroSessionManager(a) {
super(a);this.setDeleteInvalidSessions(true);
}
/** * Shiro will retrieve the Value(Token) of the Authorization key from the request header@Author Sans
* @CreateTime 2019/6/13 8:47
*/
@Override
public Serializable getSessionId(ServletRequest request, ServletResponse response) {
String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// If a token exists in the request header, the token is obtained from the request header
if(! StringUtils.isEmpty(token)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return token;
} else {
// Disable Cookie fetching
// Fetch the Token from the Cookie according to the default rules
// return super.getSessionId(request, response);
return null; }}}Copy the code
Create the ShiroConfig configuration class
/ * * *@DescriptionShiro configuration 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:";
/ / Redis configuration
@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 *@AttentionTo use proxy mode, you need to enable code support *@Author Sans
* @CreateTime2019/6/12 8:38 * /
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/** * Shiro base configuration *@Author Sans
* @CreateTime2019/6/12 8:42 * /
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// Note that the filter configuration order cannot be reversed
// Configure filtering: links that cannot be blocked
filterChainDefinitionMap.put("/static/**"."anon");
filterChainDefinitionMap.put("/userLogin/**"."anon");
filterChainDefinitionMap.put("/ * *"."authc");
// Set the default shiro login interface address. In the separation of front and back ends, the login interface hop should be controlled by the front-end route, and only JSON data is returned in the background
shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/** * Security manager *@Author Sans
* @CreateTime2019/6/12 joyfully * /
@Bean
public SecurityManager securityManager(a) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// Customize Ssession management
securityManager.setSessionManager(sessionManager());
// Custom Cache implementation
securityManager.setCacheManager(cacheManager());
// Custom Realm validation
securityManager.setRealm(shiroRealm());
return securityManager;
}
/** * Authenticator *@Author Sans
* @CreateTime2019/6/12 he * /
@Bean
public ShiroRealm shiroRealm(a) {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
/** * The credential matcher passes the password verification to Shiro's SimpleAuthenticationInfo for processing, where the matching configuration is done *@Author Sans
* @CreateTime2019/6/12 he * /
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(a) {
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;
}
/** * Configure the Redis manager *@AttentionThe shro-Redis open source plug-in is used@Author Sans
* @CreateTime2019/6/12 11:06 * /
@Bean
public RedisManager redisManager(a) {
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 *@AttentionThe shro-Redis open source plug-in is used@Author Sans
* @CreateTime2019/6/12 children of * /
@Bean
public RedisCacheManager cacheManager(a) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// To configure caching, the entity class in session must have an ID
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/** * SessionID generator *@Author Sans
* @CreateTime2019/6/12 * /
@Bean
public ShiroSessionIdGenerator sessionIdGenerator(a){
return new ShiroSessionIdGenerator();
}
/** * RedisSessionDAO *@AttentionThe shro-Redis open source plug-in is used@Author Sans
* @CreateTime2019/6/12 "* /
@Bean
public RedisSessionDAO redisSessionDAO(a) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(timeout);
return redisSessionDAO;
}
/** * Configure Session manager *@Author Sans
* @CreateTime2019/6/12 now * /
@Bean
public SessionManager sessionManager(a) {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setSessionDAO(redisSessionDAO());
returnshiroSessionManager; }}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:
Note the name | instructions |
---|---|
RequiresAuthentication | The current Subject must be authenticated in the current session when classes, methods, etc., annotated with this annotation are accessed. |
RequiresGuest | Classes, methods, etc., annotated with this annotation can be accessed as “gust” and do not need to be authenticated or recorded in the previous session. |
RequiresUser | To verify that a user is remembered, there are two meanings: first, if the user is successfully logged in (subject.isauthenticated () is true); The other isRemembered(subject.isremembered () results true). |
RequiresPermissions | The current Subject needs to have certain permissions in order to execute the method annotated by the annotation. Without permission, the method will not execute and an AuthorizationException will be thrown. |
RequiresRoles | The current Subject must have all the specified roles in order to access the methods annotated by the annotation. If there is no role, the method will not execute and an AuthorizationException will be thrown. |
RequiresPermissions AND RequiresRoles are the most commonly used permissions for projects, allowing multiple roles AND permissions. The default logic is AND, which allows access to methods only if you have both. You can specify OR as an argument in annotations | |
“` java | |
The sample | |
// Have a role to access | |
@RequiresRoles(value={“ADMIN”,”USER”},logical = Logical.OR) | |
// Only with all permissions | |
@RequiresPermissions(value={“sys:user:info”,”sys:role:info”},logical = Logical.AND) | |
` ` ` | |
Order of use :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 intercession is RequiresRoles->RequiresPermissions->RequiresAuthentication-> | |
RequiresUser->RequiresGuest | |
“` java | |
The sample | |
// Have the ADMIN role and sys:role:info permission | |
@RequiresRoles(value={“ADMIN”) | |
@RequiresPermissions(“sys:role:info”) | |
` ` ` | |
Create the UserRoleController role to intercept the test class | |
“` java | |
/ * * |
- @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 UserMenuController permission blocking test class Java /** * @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
/ * * *@DescriptionUser login@Author Sans
* @CreateTime2019/6/17 15:21 * /
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {
/** * login *@Author Sans
* @CreateTime2019/6/20 9:21 and * /
@RequestMapping("/login")
public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){
Map<String,Object> map = new HashMap<>();
// Perform authentication
try{
// Verify identity and login
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());
// Verify that the login is successful
subject.login(token);
}catch (IncorrectCredentialsException e) {
map.put("code".500);
map.put("msg"."User does not exist or password is incorrect");
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"."This 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 successful");
map.put("token",ShiroUtils.getSession().getId().toString());
return map;
}
/** * Not logged in *@Author Sans
* @CreateTime2019/6/20 9:22 * /
@RequestMapping("/unauth")
public Map<String,Object> unauth(a){
Map<String,Object> map = new HashMap<>();
map.put("code".500);
map.put("msg"."Not logged in");
return map;
}
/** * add a user demo interface * this is only used as demo without any permissions and repeated query verification *@Author Sans
* @CreateTime2020/1/6 9:22 * /
@RequestMapping("/testAddUser")
public Map<String,Object> testAddUser(a){
// Set basic parameters
SysUserEntity sysUser = new SysUserEntity();
sysUser.setUsername("user1");
sysUser.setState("NORMAL");
// Randomly generate salt values
String salt = RandomStringUtils.randomAlphanumeric(20);
sysUser.setSalt(salt);
// Encrypt
String password ="123456";
sysUser.setPassword(SHA256Util.sha256(password, sysUser.getSalt()));
// Save the user
sysUserService.save(sysUser);
// Save the role
SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity();
sysUserRoleEntity.setUserId(sysUser.getUserId()); // After the user is saved, the ID is returned to the user entity
sysUserRoleService.save(sysUserRoleEntity);
// Return the result
Map<String,Object> map = new HashMap<>();
map.put("code".0);
map.put("msg"."Added successfully");
returnmap; }}Copy the code
5. 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 invalidWhen 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 againAccess 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
Vi. Follow-up supplement
As SpringBoot releases become more advanced, the Sirio-reids plugin has been incompatibilities since 2.2.1. For more information, go to github.com/alexxiyang/…
The fix: Thanks to Github’s Manondidi for the fix. Go to github.com/manondidi/s… Replace the Sirio-redis part of the POM with
<groupId>com.github.manondidi</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.10</version>
Copy the code
Seven. Project source code
Yards cloud: gitee.com/liselotte/s…
GitHub:github.com/xuyulong201…
Thank you for reading this article. If you like it, please click “like” and give more stars. Please give your valuable opinions on the shortcomings of this article.