Shiro permission control login authentication


Foreword: Most of you are new to Shiro’s framework, so we’ll start with the basics. Of course, I’ll throw away shiro’s official graphics (does anyone really read that stuff?). To explain shiro’s configuration process and simple implementation of login authentication step by step

Shiro is used to help us with permission management. Shiro for this article is used in Web projects, so I used the latest Spring Boot framework. (Of course, using XML for configuration is also possible, the principle is the same, but the writing is different.)

Understand what Shiro’s permission management is before you begin? Shiro’s main features include authentication, authorization, encryption, session management, caching, and a host of other features that will make you feel uninterested in learning

Authentication is login authentication: when you log in to the web page, Shiro will authenticate you with a password (we use token here). Of course, you will also use this password to get the server’s approval for subsequent permission operations. Authorization is permission acceptance: Shiro will authenticate you based on the information you provide and give you corresponding rights (such as delete, add, etc.);

Keep in mind that Shiro will not create and maintain relational tables for you. Instead, we need to create our own relational tables in the database: Users — roles — permissions

2. Roles

3. Permission table

Users and roles is a one-to-many relationship, a user can have multiple roles (such as administrator, ordinary users) roles and permissions is a many-to-many relationship, a character can use multiple access, a permission can also correspond to multiple users And, of course, the associated table, said there is not much, because we only do login authentication, so now you just need to a user list

So what is login authentication, I think a lot of beginners will misinterpret it, it does not help you to log in to the username account. To really understand it, we need to know what Shiro is for. What role does login authentication play in Shiro?

Shiro is used to manage permissions, so how do you make Shiro remember you after you log in? That’s what login authentication is for. So some of you might ask, why do you use Shiro authentication instead of using the user table in the database? I asked this question, to continue to understand will know: because every time you after operation with a server returned to your data to check, if you use a User table data is highly safe and reliable, since joining the shiro framework, consider to the safety, so we can use token to check, it is also the focus of this article!

Without further ado, let’s get started:

Step 1: Import related packages

Here I use Maven for package management:

<? The 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" > The < modelVersion > 4.0.0 < / modelVersion > < groupId > cn. LXT < / groupId > < artifactId > demo < / artifactId > <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name> Demo </name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the boot - starter - parent < / artifactId > < version > 1.5.8. RELEASE < / version > < relativePath / > <! -- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> < project. Reporting. OutputEncoding > utf-8 < / project. Reporting. OutputEncoding > < Java version > 1.8 < / Java version > </properties> <dependencies> <! --spring boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> < artifactId > spring - the boot - starter - thymeleaf < / artifactId > < version > 1.5.8. RELEASE < / version > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> < version > 1.5.8. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework. Boot < / groupId > < artifactId > spring - the boot - starter - data - jpa < / artifactId > < version > 1.5.8. RELEASE < / version > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> < version > 1.5.8. RELEASE < / version > < / dependency > <! > <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>1.5.8.RELEASE</version> <optional>true</optional> <scope>true</scope> </dependency> <! --mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> < artifactId > mybatis - spring - the boot - starter < / artifactId > < version > 1.3.1 < / version > < / dependency > < the dependency > < the groupId > mysql < / groupId > < artifactId > mysql connector - Java < / artifactId > < version > 5.1.38 < / version > < / dependency > <dependency> <groupId>org.mybatis</ artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> The < version > 1.3.5 < / version > < / dependency > <! --aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> < version > 1.5.8. RELEASE < / version > < / dependency > <! -- dependency> <groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.5</version> </dependency> <! --shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> < version > 1.3.2 < / version > < / dependency > < the dependency > < groupId >. Org. Apache shiro < / groupId > < artifactId > shiro - ehcache < / artifactId > < version > 1.3.2 < / version > < / dependency > < the dependency > < groupId > org, apache shiro < / groupId > < artifactId > shiro - cas < / artifactId > < version > 1.3.2 < / version > < / dependency > </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> </plugin> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> < Configuration > <verbose>true</verbose> <overwrite>true</overwrite> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> </dependency> </dependencies> </plugin> </plugins> </build> </project>Copy the code

Step 2: Configure Shiro

With poM configured, it is time to write Shiro’s global configuration classes in Java. Before configuring Shiro we need to understand its three main elements: Subject: a single object, a user object that interacts with how the application is applied; SecurityManager: SecurityManager, managing Subject; Realm: Domain, SecurityManager interacts with Realm to get data (user-role-permission)

With this in mind, we will create a new ShiroConfig class :(since this article will only learn login authentication, we will not use cache management, password encoding, etc.)

package cn.lxt.shiro; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; @ Configuration public class shiroConfig {/ * * * * is responsible for shiroBean life cycle / @ Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } /** * This is a custom authentication class that inherits AuthorizingRealm, Responsible for handling user authentication and permission * / @ Bean @ DependsOn (" lifecycleBeanPostProcessor ") public MyShiroRealm shiroRealm () {MyShiroRealm realm = new MyShiroRealm(); //realm.setCredentialsMatcher(hashedCredentialsMatcher()); return realm; } /** Add realm to securityManager * @return */ @bean public securityManager securityManager(){ / / note is DefaultWebSecurityManager!!!!!! DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroRealm()); return securityManager; } /** ShiroFilter factory class * 1. Define ShiroFilterFactoryBean * 2. Set SecurityManager * 3. Configure interceptor * 4. ShiroFilterFactoryBean */ @bean public ShiroFilterFactoryBean shiroFilter(SecurityManager SecurityManager){//1 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); / / 2 / / registered securityManager shiroFilterFactoryBean. SetSecurityManager (securityManager); System.out.println("11"); //LinkHashMap is in order, Shiro intercepts Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // all urls can be accessed anonymously //authc: All urls must be certified by can access / / user, configuration to remember me or to access authentication by / / logout, log out filterChainDefinitionMap. Put ("/JQuery / * * ", "-anon"); filterChainDefinitionMap.put("/js/**","anon"); / / configuration exit filter filterChainDefinitionMap. Put ("/example1 ", "-anon"); filterChainDefinitionMap.put("/lxt","anon"); filterChainDefinitionMap.put("/login","anon"); filterChainDefinitionMap.put("/success","anon"); filterChainDefinitionMap.put("/index","anon"); filterChainDefinitionMap.put("/Register","anon"); filterChainDefinitionMap.put("/logout","logout"); / / custom filtering connections, from above order, so use LinkHashMap / * * filterChainDefinitionMap. The one on the bottom put (" / * * ", "authc"); / / set the login screen, if you don't set to search for the web root directory file shiroFilterFactoryBean. SetLoginUrl ("/LXT "); / / set to jump after a successful login connection shiroFilterFactoryBean. SetSuccessUrl ("/success "); / / set the login failed, no permissions interface shiroFilterFactoryBean. SetUnauthorizedUrl (" / 403 "); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println(" Shiro intercepted factory injection class successfully "); // return shiroFilterFactoryBean; }}Copy the code

The above points to note: 1. ShiroFilter is entrance, there are mainly four step operation, code has comments clear 2. ShiroFilterFactoryBean. SetLoginUrl (“/LXT “); Startup class No matter what URL you type, it will jump to the login startup class; 3.shiroFilterFactoryBean.setSuccessUrl(“/success”); Login after the successful jump to the class, this method we can not take care of, because I feel it does not use, big god do not spray!

Step 3: Configure the Realm

After looking at the ShiroConfig class, many people will ask: Eeeeee! My MyShiroRealm can’t be imported! We need to write a Realm class that inherits AuthorizingRealm. After inheritance, we need to override two methods: 1. DoGetAuthorizationInfo () is used to control roles and permissions. 2. DoGetAuthenticationInfo () method is used to login to the certification, the point. Post the code below:

package cn.lxt.shiro; import cn.lxt.bean.User; import cn.lxt.service.UsersService; 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.springframework.beans.factory.annotation.Autowired; public class MyShiroRealm extends AuthorizingRealm { @Autowired private UsersService usersService; @param principalCollection * @return */ @override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * Verify the currently logged Subject * @param token * @return * @throws AuthenticationException */ @override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// Get the account (username) String username = (String) token.getPrincipal(); System.out.println("username=:"+username); System.out.println(token.getCredentials()); User user = usersService.findByName(username); if (user==null){ return null; } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),getName()); return info; }}Copy the code

The token is passed in as a token. The token is passed in as a token. The token is passed in as a token.

Of course, some students still see the clouds and fog here, here I will explain some ideas: 1. When we log in with the account and password, we create a token (token is just a concept, the specific implementation still needs to be defined) into the database. 2. When storing token, remember that it is randomly generated and will be bound to the user login ID after generation. 3. Therefore, after login, the JSON object returned to the browser should contain the token value, and the browser will store the token value in the browser.

Step 4: Token creation and transfer

1. Create token:

package cn.lxt.controller; import cn.lxt.bean.User; import cn.lxt.service.TokenService; import cn.lxt.service.UsersService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.util.Map; @Controller public class LoginController { @Autowired private UsersService usersService; @Autowired private TokenService tokenService; @postmapping (value = "/login") @responseBody public map <String, Object> login(HttpServletRequest request){ String username = request.getParameter("name"); User user = usersService.findByName(username); // Create a token Map for this User <String,Object> Map = tokenService.createToken(user.getid ()); map.put("user",user); return map; }}Copy the code

Return a User and Token to the front end in the controller; Create token in Service and store it in database:

package cn.lxt.service.Impl; import cn.lxt.bean.Token; import cn.lxt.dao.TokenMapper; import cn.lxt.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; @Service public class TokenServiceImple implements TokenService{ private final static int EXPIRE = 3600*12; @Autowired private TokenMapper tokenMapper; @override // Incomplete function: check whether it already exists, Public Map createToken(int userId) {Map<String,Object> Map = new HashMap<String, Object>(); // Set a randomly generated token String token = uuid.randomuuid ().toString(); CreateTime = new Date(); Date expireTime = new Date(createTime.getTime()+EXPIRE*1000); / / to determine whether a token existing / / here to omit the first token tokenEntity = new token (userId, token, expireTime, createTime); // Save to database tokenmapper. insert(tokenEntity); System.out.println(" save successfully "); map.put("token",token); return map; }}Copy the code

When creating the token above, we did not determine whether the token of user Id already exists in the database due to time reasons. You can try it yourself. 3.OK, we have created the token and passed it in JSON format, now all we need to do is save the token in the browser: on the login button of the login screen, we set a js method:

function login() { $.ajax({ url:"/login", type:"post", dataType:"JSON", data:$(".login").serialize(), success:function (map) { if(map.token! =null){ localStorage.setItem("token",map.token); window.location.href="/success"; }else{alert("token is empty "); }}})}Copy the code

The above code passes the token to localStorage.

But if you’re careful, you’ll notice that in the Realm code, when you pass in the parameter (AuthenticationToken token) it gets the token from the Header, and our token is stored in localStorage, so now we need to access any URL, Can transfer the token from localStorage to Header, this problem is left to the smart you, if you really can’t do it, you can send me a private message (pay attention to I will reply quickly, really).

This is a simple implementation of Shiro security framework login authentication on Spring Boot. If you think it is ok, please give a thumbs-up. If you can’t, you can collect it. Anyway, thanks for reading ~