The source code address is added at the end of the article. Friends who report errors can download it
- updated on June 15, 2020
At the request of netizens, the code of this pom file has been pasted completely,
The springboot version uses the latest 2.3.4.RELEASE,
shiro version uses the latest 1.6.0
The pro test is valid and will be updated continuously
- updated on September 20, 2020
Added some common exceptions to shiro
- updated on September 20, 2020
##1. What is Shiro?
Shiro is an open source project under Apache. Shiro is a lightweight framework, which is much simpler and less complex than spring security. The following is the record of my own study. The official structure is as follows:##2. Main functions
shiro mainly has three functional modules:
1. Subject: subject, which generally refers to the user. 2. SecurityManager: Security Manager, which manages all subjects and can cooperate with internal security components. (similar to the dispatcher servlet in spring MVC) 3. Realms: used to verify permission information, which generally needs to be implemented by yourself.3. Subdivision function
1. Authentication: identity authentication / login (account password verification). 2. Authorization: authorization, that is, role or permission verification. 3. Session Manager: session management, session related management after user login. 4. Cryptography: encryption, password encryption, etc. 5. Web Support: web support, integrated web environment. 6. Caching: caching user information, roles, permissions, etc. into a cache such as redis. 7. Concurrency: multithread concurrency verification. When another thread is started in one thread, the permissions can be automatically propagated in the past. 8. Testing: test support; 9. Run As: allow one user to access as another user (if they allow it). 10. Remember Me: after logging in, you don't have to log in next time.(for more information on what shiro is, please go to the search engine. This article mainly records the integration of springboot and shiro.)
First, create the springboot project, which is not described here.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> </parent> <groupId>com.wsl</groupId> <artifactId>spring-shiro-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-shiro-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring.shiro.version>1.6.0</spring.shiro.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>$</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--Page template dependency--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--Hot deployment dependency--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>User.java (user entity class):
package com.wsl.bean; import lombok.AllArgsConstructor; import lombok.Data; import java.util.Set; @Data @AllArgsConstructor public class User { private String id; private String userName; private String password; /** * Role set corresponding to user */ private Set<Role> roles; }Role.java (entity class corresponding to role):
package com.wsl.bean; import lombok.AllArgsConstructor; import lombok.Data; import java.util.Set; @Data @AllArgsConstructor public class Role { private String id; private String roleName; /** * Permission set corresponding to role */ private Set<Permissions> permissions; }Permissions.java (entity class corresponding to permission):
package com.wsl.bean; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Permissions { private String id; private String permissionsName; }LoginServiceImpl.java:
package com.wsl.service.impl; import com.wsl.bean.Permissions; import com.wsl.bean.Role; import com.wsl.bean.User; import com.wsl.service.LoginService; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @Service public class LoginServiceImpl implements LoginService { @Override public User getUserByName(String getMapByName) { return getMapByName(getMapByName); } /** * Simulate database query * * @param userName user name * @return User */ private User getMapByName(String userName) { Permissions permissions1 = new Permissions("1", "query"); Permissions permissions2 = new Permissions("2", "add"); Set<Permissions> permissionsSet = new HashSet<>(); permissionsSet.add(permissions1); permissionsSet.add(permissions2); Role role = new Role("1", "admin", permissionsSet); Set<Role> roleSet = new HashSet<>(); roleSet.add(role); User user = new User("1", "wsl", "123456", roleSet); Map<String, User> map = new HashMap<>(); map.put(user.getUserName(), user); Set<Permissions> permissionsSet1 = new HashSet<>(); permissionsSet1.add(permissions1); Role role1 = new Role("2", "user", permissionsSet1); Set<Role> roleSet1 = new HashSet<>(); roleSet1.add(role1); User user1 = new User("2", "zhangsan", "123456", roleSet1); map.put(user1.getUserName(), user1); return map.get(userName); } }Custom Realm is used to query the user's role and permission information and save it to the permission Manager: CustomRealm.java
package com.wsl.shiro; import com.wsl.bean.Permissions; import com.wsl.bean.Role; import com.wsl.bean.User; import com.wsl.service.LoginService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; public class CustomRealm extends AuthorizingRealm { @Autowired private LoginService loginService; /** * @MethodName doGetAuthorizationInfo * @Description Permission configuration class * @Param [principalCollection] * @Return AuthorizationInfo * @Author WangShiLin */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //Get login user name String name = (String) principalCollection.getPrimaryPrincipal(); //Query user name User user = loginService.getUserByName(name); //Add roles and permissions SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (Role role : user.getRoles()) { //Add role simpleAuthorizationInfo.addRole(role.getRoleName()); //add permission for (Permissions permissions : role.getPermissions()) { simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName()); } } return simpleAuthorizationInfo; } /** * @MethodName doGetAuthenticationInfo * @Description Authentication configuration class * @Param [authenticationToken] * @Return AuthenticationInfo * @Author WangShiLin */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { if (StringUtils.isEmpty(authenticationToken.getPrincipal())) { return null; } //Get user information String name = authenticationToken.getPrincipal().toString(); User user = loginService.getUserByName(name); if (user == null) { //The corresponding exception will be reported after returning here return null; } else { //The information of authenticationToken and simpleAuthenticationInfo is verified here SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName()); return simpleAuthenticationInfo; } } }ShiroConfig.java, inject CustomRealm and SecurityManager into the spring container:
package com.wsl.config; import com.wsl.shiro.CustomRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class shiroConfig { @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } //Add your own authentication method to the container @Bean public CustomRealm myShiroRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } //Permission management and configuration are mainly the management and authentication of Realm @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } //Filter factory, set the corresponding filter conditions and jump conditions @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> map = new HashMap<>(); //Logout map.put("/logout", "logout"); //Authenticate all users map.put("/**", "authc"); //Sign in shiroFilterFactoryBean.setLoginUrl("/login"); //home page shiroFilterFactoryBean.setSuccessUrl("/index"); //Error page, authentication failed, jump shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }LoginController.java: we write a simple login method, an index page query method, an add method and an admin method, which correspond to different roles or permissions
package com.wsl.controller; import com.wsl.bean.User; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class LoginController { @GetMapping("/login") public String login(User user) { if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) { return "Please enter user name and password!"; } //User authentication information Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken( user.getUserName(), user.getPassword() ); try { //For verification, you can catch exceptions and return the corresponding information subject.login(usernamePasswordToken); // subject.checkRole("admin"); // subject.checkPermissions("query", "add"); } catch (UnknownAccountException e) { log.error("User name does not exist!", e); return "User name does not exist!"; } catch (AuthenticationException e) { log.error("Wrong account or password!", e); return "Wrong account or password!"; } catch (AuthorizationException e) { log.error("No permission!", e); return "No permission"; } return "login success"; } @RequiresRoles("admin") @GetMapping("/admin") public String admin() { return "admin success!"; } @RequiresPermissions("query") @GetMapping("/index") public String index() { return "index success!"; } @RequiresPermissions("add") @GetMapping("/add") public String add() { return "add success!"; } }If the annotation verifies the role and permission, it cannot catch the exception, so it cannot correctly return the error message to the front end. Therefore, I added a class to intercept the exception. The specific code is as follows MyExceptionHandler.java
package com.wsl.filter; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice @Slf4j public class MyExceptionHandler { @ExceptionHandler @ResponseBody public String ErrorHandler(AuthorizationException e) { log.error("Permission verification failed!", e); return "Permission verification failed!"; } }
Open web page http://localhost:8080/login?userName=wsl&password=123456
Then enter the index address: http://localhost:8080/index
Change zhangsan account and log in before accessing index
http://localhost:8080/login?userName=zhangsan&password=123456
http://localhost:8080/index
Recently, many people have reported project errors. I put the source code on the code cloud and you can download it yourself:
Source address
Shiro needs to throw an exception if authentication fails during login authentication. AuthenticationException contains the following subclasses:
1.1. Credentialsexception voucher exceptionIncorrectCredentialsException Incorrect voucher
ExpiredCredentialsException Voucher expiration
ConcurrentAccessException: concurrent access exception (thrown when multiple users log in at the same time)
UnknownAccountException: unknown account
ExcessiveAttemptsException: the number of authentication times exceeds the limit
DisabledAccountException: Disabled account
LockedAccountException: the account is locked
Unsupported tokenexception: Unsupported Token used
Shiro needs to throw an exception when authorization fails during login authentication. AuthorizationException contains the following subclasses:
2.1. UnauthorizedException:Thrown to indicate that the requested operation or access to the requested resource is not allowed.
2.2. UnanthenticatedException:An exception is thrown when attempting to perform an authorization operation when successful authentication has not been completed.