This is the 23rd day of my participation in Gwen Challenge

Spring Boot integrates Shiro

I. Environment construction

  1. Maven rely on
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <fastjson.version>1.2.47</fastjson.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-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.0</version>
    </dependency>
	<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
	<! -- Shiro and Spring integration -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
	<! Shiro and Redis implement sessionDao -->
    <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>
Copy the code

Second, login, permission filter implementation

Controller Login Method

@RequestMapping(value="/login")
    public String login(String username,String password) {
	    // Construct the login token
        UsernamePasswordToken upToken = new UsernamePasswordToken(username,password);
        try {

            /** * Password encryption: * MD5 encryption provided by Shiro * Md5Hash: * Parameter one: encrypted content * Parameter two: salt * Parameter three: encrypted times */
            password = new Md5Hash(password, username, 3).toString();

            / / 1. Access to the subject
            Subject subject = SecurityUtils.getSubject();
            System.out.println("User Login");

            //2. Call subject to log in
            subject.login(upToken);
            return "Login successful" + sid;
        } catch (Exception e) {
            return "Wrong username or password"; }}Copy the code

Custom realm

  1. process
    • Here, the database is used to query the User object based on the user name and put the User object as security data into the SimpleAuthenticationInfo object
    • So the same is true of the object that you extract when you authorize
    • When authorizing, it is necessary to find out permission roles from the database and save them separately
  2. CustomRealm
package cn.itcast.shiro.realm;

import cn.itcast.shiro.domain.Permission;
import cn.itcast.shiro.domain.Role;
import cn.itcast.shiro.domain.User;
import cn.itcast.shiro.service.UserService;
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;

import java.util.HashSet;
import java.util.Set;

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    public void setName(String name){
        super.setName("CustomRealm");
    }

    /** * Authorization * Whether the user has the corresponding permissions during operation ** Authentication first -- security data * reauthorization -- obtain all operation permissions of the user based on security data *@param principals
     * @return* /
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //1. Obtain the authenticated user data
        User user = (User) principals.getPrimaryPrincipal(); // Get unique security data
        //2. Obtain user permissions based on user data (all roles, all permissions)
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> roles = new HashSet<>();
        Set<String> perms = new HashSet<>();
        for (Role role : user.getRoles()){
            roles.add(role.getName());
            for (Permission perm : role.getPermissions()){
                perms.add(perm.getCode());
            }
        }
        info.setStringPermissions(perms);
        info.setRoles(roles);
        return info;
    }

    /** * Authentication *@param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1. Login user name and password
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());
        //2. Query the database based on the user name
        User user = userService.findByName(username);
        //3. Check whether the user exists or the password is the same
        if(user ! =null && user.getPassword().equals(password)){
            //4. If the security data is consistently returned
            // Constructor: secure data (self constructed object), password, realm name
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
            return info;
        }
        //5. Inconsistent, return null (throw exception)
        return null; }}Copy the code

Shiro configuration file

  • Configure the securityManager using Spring Boot
  1. ShiroConfiguration
package cn.itcast.shiro;

import cn.itcast.shiro.realm.CustomRealm;
import cn.itcast.shiro.session.CustomSessionManager;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {
    / / 1. Create a realm
    @Bean
    public CustomRealm getRealm(a){
        return new CustomRealm();
    }

    2. Create a security manager
    @Bean
    public SecurityManager getSecurityManager(CustomRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);

        return securityManager;
    }

    //3. Configure shiro filters

    /** * In web applications, Shiro controls all permissions through a set of filters * map to filter different addresses, and value to determine which filters *@param securityManager
     * @return* /
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        //1. Create filter factories
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2. Set the security manager
        filterFactory.setSecurityManager(securityManager);
        //3. General configuration (login page, which is an authorized login page)
        filterFactory.setLoginUrl("/autherror? code=1");// Failed to log in to the url
        filterFactory.setUnauthorizedUrl("/autherror? code=2");/ / not authorized
        //4. Set the filter set

        /** * Set all filters: sequential map * key = blocked URL * value = filter type */
        Map<String,String> filterMap = new LinkedHashMap<>();
// filterMap.put("/user/home", "anon"); // The current requested address can be accessed anonymously

        // Have certain permissions to access
        // Configure permission filtering as a filter
// filterMap.put("/user/home", "perms[user-home]"); // If you do not have permission, jump to the unauthorized address set above

        // You must have a certain role to log in
// filtermap. put("/user/home", "roles[sysadmin]");
        filterMap.put("/user/**"."authc");  // The current requested address must be authenticated to be accessible

        filterFactory.setFilterChainDefinitionMap(filterMap);

        return filterFactory;
    }

    // Configure Shiro annotation support
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        returnadvisor; }}Copy the code

The type of filter in Shiro

Methods of authorization

  • It’s basically setting up a map
  • The map contains interface information and permission information
  • Finally, put the map into an object
/** * Set all filters: sequential map * key = blocked URL * value = filter type */
Map<String,String> filterMap = new LinkedHashMap<>();
// filterMap.put("/user/home", "anon"); // The current requested address can be accessed anonymously

// Have certain permissions to access
// Configure permission filtering as a filter
// filterMap.put("/user/home", "perms[user-home]"); // If you do not have permission, jump to the unauthorized address set above

// You must have a certain role to log in
// filtermap. put("/user/home", "roles[sysadmin]");
filterMap.put("/user/**"."authc");  // The current requested address must be authenticated to be accessible

filterFactory.setFilterChainDefinitionMap(filterMap);
Copy the code

Annotation-based authorization

  1. RequiresPermissions
    • Configured to a method indicating the specified permissions that the method must have to execute
/ / query
@RequiresPermissions(value = "user-find")
public String find(a) {
    return "User query succeeded";
}
Copy the code
  1. RequiresRoles
    • Configured to a method, indicating that executing the method must have the specified role
/ / query
@requiresroles (value = "system administrator ")
public String find(a) {
    return "User query succeeded";
}
Copy the code

Session management

What is session management

In Shiro, all user session information is controlled by Shiro. Shiro provides sessions that can be used in JavaSE/JavaEE environment, independent of any underlying container, and can be used independently as a complete session module. Unified session management through Shiro’s SessionManager

If not, what is the state of the session?

  • If you don’t set session as you did before, then after the user login, all data will be stored in the HttpSession, and a sessionId will be returned, stored in the cookie

Build environment

  1. Shiro and REids integration dependencies
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.0.0</version>
</dependency>
Copy the code
  1. Add redis configuration to the SpringBoot configuration file
redis:
    host: 192.16885.171.
    port: 6379
Copy the code

Customize our sessionManager

package cn.itcast.shiro.session;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/** * custom sessionManager */
public class CustomSessionManager extends DefaultWebSessionManager {

    /** * specifies the method of obtaining the sessionId. * the sessionId * request header information contains the Authorization: sessionId */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        // Get the data in the request header
        String id = WebUtils.toHttp(request).getHeader("Authorization");
        if(StringUtils.isEmpty(id)){
            // if the first access does not have a sessionId, generate a sessionId
            // the parent method can generate a sessionId, so call the parent method directly
            return super.getSessionId(request, response);
        }else{
            / / returns the sessionId
            // Where does the sessionId come from: the request header
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
            // What is the sessionId
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            // Does this sessionId need validation
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            returnid; }}}Copy the code

Configure Redis Session management

/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the session management -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /

    /** * Why put session in redis? * 1. If the session is not stored in redis, the session is stored in the memory of the current service and cannot be accessed by other services. Obviously, for distributed microservices, each service can synchronize data * 3. If you don't put it in reids, it's like a normal session */
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;

    //1. Controller of redis, operating redis
    public RedisManager redisManager(a){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setPassword("123456");
        return redisManager;
    }

    //2. sessionDao
    public RedisSessionDAO reidsSessionDao(a){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        // If you look at shiro's architecture diagram, you can see that reidsDao is managed using RedisManager
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    //3. Session management
    public DefaultWebSessionManager sessionManager(a){
        CustomSessionManager sessionManager = new CustomSessionManager();
        // This step is similar to the above logic
        sessionManager.setSessionDAO(reidsSessionDao());
        return sessionManager;
    }


    //4. Cache manager
    public RedisCacheManager cacheManager(a){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    / * -- -- -- -- -- -- -- -- -- -- -- -- -- end of the session management configuration -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
Copy the code