Apache Shiro is already well-known for Java, similar to authentication form authentication in.Net.The authentication and authorization policies in.net core are basically the same.Of course, it doesn't matter if you don't know, because all permissions are simulated social behavior of people or institutions.
Starting with simple permissions, this series mainly covers Shiro, Spring Security, Jwt, OAuth2.0, and other custom permission policies.
This chapter mainly explains the basic principles of Shiro and how to use it. It is mainly used in the following infrastructure:
- jdk1.8+
- spring boot 2.1.6
- idea 2018.1
Source Download for this project
1 Spring Boot Quick Integrated Shiro Example
Let's start with a real-world code demonstrating how Spring Boot integrates Shiro.This code sample does not use database-related knowledge at this time. This code mainly uses:
- shiro
- thymeeaf
This example demonstrates that a user of the site user admin password 123456 logs on to the site with a username password, gets a list of authorized rights after Shiro authentication, and demonstrates the use of rights.
Source Download for this project
1.1 New Spring Boot Maven Sample Project
- File > New > Project, select Spring Initializr as shown below and click Next to proceed
- Fill in GroupId (package name), Artifact (project name).Click Next
groupId=com.fishpro
artifactId=shiro - Choose to rely on Spring Web Starter to hook in front.
- The project name is set to spring-boot-study-shiro.
1.2 Dependency Introducing Pom.xml
This code mainly uses:
- shiro
- thymeeaf
Add the following code to Pom.xml
<?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.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.fishpro</groupId> <artifactId>shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <name>shiro</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--shiro 1.4.0 thymeleaf-extras-shiro 2.0.0 combination--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!--shiro for thymeleaf Effectiveness requires joining spring boot 2.x Please use 2.0.0 Version otherwise use 1.2.1 Edition--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <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> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
1.3 Configure application.yml
This example uses the default configuration and does not need to configure shiro in application.yml at this time.We changed application.properties to application.yml and changed the default port
server: port: 8086
1.4 Custom Realm realm UserRealm implements custom authentication and authorization
Add UserRealm.java file to src/main/java/com/java/fishpro/shiro/config (config is new package name)
UserRealm is a secure data source in which the user login authentication core is implemented and user authorization is also implemented, see Code Notes
- Overrides the doGetAuthenticationInfo implementation to authenticate the username password and returns a SimpleAuthenticationInfo object.*Note that because shiro is a security framework, specific authentication of identity certificates is left to us to implement. In fact, authentication is business logic, and it is best for us to implement it ourselves.
- Overrides the doGetAuthorizationInfo implementation's authorization to the current user and returns a SimpleAuthorizationInfo object. Note that authorization refers to querying the business system database for a list of known privileges of the current user and writing it in the current session to match when used. A successful match indicates that the authorization was successfulMatching failure indicates no authorization
//Define an entity object to store user information public class UserDO { private Integer id; private String userName;//Is the identity in shiro, the only existence in the system private String password; //Is proof in shiro public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Create UserRealm under package config (no new config created)
package com.fishpro.shiro.config; import com.fishpro.shiro.domain.UserDO; import org.apache.shiro.SecurityUtils; 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.session.Session; import org.apache.shiro.subject.PrincipalCollection; import java.security.Principal; import java.util.*; /** * The user realm has rewritten AuthorizingRealm, which actually inherits Authenticating Realm * Where it is, as long as it inherits AuthorizingRealm, it mainly implements method rewriting of authorization and certification * 1.doGetAuthenticationInfo Rewrite Authentication * 2.doGetAuthorizationInfo Override Authorization * */ public class UserRealm extends AuthorizingRealm { /** * doGetAuthenticationInfo Rewrite Authentication * @param authenticationToken token * @return Return AuthenticationInfo for Authentication Information Entities (Good Identity and Proof) * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String username=(String)authenticationToken.getPrincipal();//Identity such as user name Map<String ,Object> map=new HashMap<>(16); map.put("username",username); String password =new String((char[]) authenticationToken.getCredentials());//Certificates such as passwords //Authentication of Identity+Proof Data Here a data source is simulated //If it is a database then the database should be called here to determine if the username password is correct if(!"admin".equals(username) || !"123456".equals(password)){ throw new IncorrectCredentialsException("Incorrect account or password"); } //Certification Passed UserDO user=new UserDO(); user.setId(1);//Assume user ID=1 user.setUserName(username); user.setPassword(password); //Establish a SimpleAuthenticationInfo authentication module including identity) certification and other information SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); return info; } /** * Override authorization doGetAuthorizationInfo to return authorization information object AuthorizationInfo * @param principalCollection Identity Information * @return Return AuthorizationInfo for Authorization Information Object * */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { UserDO userDO = (UserDO)principalCollection.getPrimaryPrincipal(); Integer userId= userDO.getId();//Convert to user object //Authorize the creation of a new authorization module SimpleAuthorizationInfo to assign permissions to the current user SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //Setting the actual scenario of roles owned by the current session Get the list of roles from the database based on business considerations Set<String> roles=new HashSet<>(); roles.add("admin"); roles.add("finance"); info.setRoles(roles); //Setting the permissions that the current session can have Actual Scenarios Get the list of permissions under the role list from the database, depending on the business Set<String> permissions=new HashSet<>(); permissions.add("system:article:article"); permissions.add("system:article:add"); permissions.add("system:article:edit"); permissions.add("system:article:remove"); permissions.add("system:article:batchRemove"); info.setStringPermissions(permissions); return info; } }
1.6 shiro for login authentication
The main display here is login.html and LoginController
The new file resources/templates/login.html represents the login page, where jquery is used to implement logic
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Use shiro Logon Page</title> </head> <body> <div> <input id="userName" name="userName" value=""> </div> <div> <input id="password" name="password" value=""> </div> <div> <input type="button" id="btnSave" value="Sign in"> </div> <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script> <script> $(function() { $("#btnSave").click(function () { var username=$("#userName").val(); var password=$("#password").val(); $.ajax({ cache: true, type: "POST", url: "/login", data: "userName=" + username + "&password=" + password, dataType: "json", async: false, error: function (request) { console.log("Connection error"); }, success: function (data) { if (data.status == 0) { window.location = "/index"; return false; } else { alert(data.message); } } }); }); }); </script> </body> </html>
1.7 shiro implements Controller layer method authorization
There are several additional pages to do this
1.7.1 resources/templates/index.html page jumped after successful login
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Jump to this page after login validation</title> </head> <body> Jump to this page after login validation <div> <a href="/article">Go to article page</a> </div> <div> <a href="/setting">Go to the settings page</a> </div> </body> </html>
1.7.2 resources/templates/article.html Authorized Access Pages
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Must Get app:article To grant authorization</title> </head> <body> //You must obtain app:article authorization to display </body> </html>
1.7.3 resources/templates/setting.html Unauthorized Access Pages
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Must Get app:setting To grant authorization </title> </head> <body> //You must obtain app:setting s authorization to display </body> </html>
1.7.4 resources/templates/error/403.html is not authorized to blow pages uniformly
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>403 No authorization</title> </head> <body> //The page you visited is not authorized </body> </html>
1.7.5 controller/UserController.java Controller layer method
The following source code
- Method article requires permission app:article:article to enter
- Method settings require permission app:set:settings to enter
@Controller public class UserController { //Default jump page after successful shiro authentication @GetMapping("/index") public String index(){ return "index"; } @GetMapping("/403") public String err403(){ return "403"; } /** * Use comment @RequiresPermissions with permission authorization * */ @GetMapping("/article") @RequiresPermissions("app:article:article") public String article(){ return "article"; } /** * Use comment @RequiresPermissions with permission authorization * */ @GetMapping("/setting") @RequiresPermissions("app:setting:setting") public String setting(){ return "setting"; } @GetMapping("/login") public String login(){ return "login"; } @PostMapping("/login") @ResponseBody public Object loginsubmit(@RequestParam String userName,@RequestParam String password){ Map<String,Object> map=new HashMap<>(); //Encapsulate identity useName and proof password as objects UsernamePasswordToken UsernamePasswordToken token=new UsernamePasswordToken(userName,password); //Get the current subject Subject subject = SecurityUtils.getSubject(); try{ subject.login(token); map.put("status",0); map.put("message","Login Successful"); return map; }catch (AuthenticationException e){ map.put("status",1); map.put("message","ERROR Incorrect username or password"); return map; } } }
1.8 shiro implements authorization on front-end pages
We use Thymeleaf as the front-end template engine, you can also use JSP, FreeMarker and other engines.Shiro is already very good for Thymeleaf, as shown in the following code I used on the first page
There are many ways to authorize with shiro
|Mode|Comment|Example|
|---|---|---|
|Verify login|@RequiresAuthentication|@RequiresAuthentication|
|Do you remember me|@RequiresUser||
|Visitor Identity|@RequiresGuest|@RequiresGuest|
|Has Role|@RequiresRoles|@RequiresRoles("admin")|
|Permissions |@RequiresPermissions|@RequiresPermissions("perm")|
Why is my comment not working?
To use the shiro annotation to authorize Controller's methods, you need to include the following code in ShiroConfig
/** *Turn on shiro aop annotation support such as @RequiresRoles,@RequiresPermissions * Use proxy; therefore, code support needs to be turned on; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
1.9 Logout/Logout
Call the logout method of the subject s to log out
@GetMapping("/logout") String logout(HttpSession session, SessionStatus sessionStatus, Model model) { //Membership Center Exit Logon When using these two properties session property to exit session.removeAttribute("userData"); sessionStatus.setComplete(); SecurityUtils.getSubject().logout(); return "redirect:/login"; }
1.10 Questions
-
Invalid @RequiresPermissions comment
Invalid comment, no comment reached, basically AOP interception problem, need to add configuration in ShiroConfig configuration
- spring boot shiro Not authorized to invoke method occurs when permissions in @RequiresPermissions do not exist
Since @RequiresPermissions is in effect, why would there be an error? You are logged in reasonably, but you do not have permission to use the method body. You should skip to page / 403.
There should be no error blocking the method here.This is in Spring Boot Global Exception Handling As mentioned in the section, you need to use an exception capture mechanism, catch the exception org.apache.shiro.authz.UnauthorizedException, and do a unified process.
@ControllerAdvice(annotations = Controller.class) public class MyExceptionController { private static final Logger logger= LoggerFactory.getLogger(MyExceptionController.class); public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = UnauthorizedException.class)//Insufficient privileges when handling access methods public String defaultErrorHandler(HttpServletRequest req, Exception e) { return "error/403"; } }
- Shi shiro:hasPermission tag does not work in thymeleaf
The shiro:hasPermission tag is used in thymeleaf because it involves two frameworks, such as the introduction of third-party controls if not supported natively.
/** * ShiroDialect,Beans to use shiro's label in thymeleaf * @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); }
- Sometimes error returns org.springframework.beans.factory.BeanCreationException: Error creation bean with name'shiroDialect'
This should be a compatibility issue with the spring boot version of the third-party plugin com.github.theborakompanioni.I changed to the following version shiro for thymeleaf to take effect requiring spring boot 2.x Please use version 2.0.0
<!--shiro 1.4.0 thymeleaf-extras-shiro 2.0.0 combination--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!--shiro for thymeleaf Effectiveness requires joining spring boot 2.x Please use 2.0.0 Version otherwise use 1.2.1 Edition--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>