springboot2.X integrates mybatis2.X+shiro1.X+JWT (auth0 3.X) to achieve stateless login and authentication

Source code address: springboot integrates mybatis+shiro+jwt(auth0) to achieve stateless login and authentication

1. Create new springboot project integration related dependencies.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tongchenggo</groupId>
    <artifactId>tongchenggo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>tongchenggo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!--        shiro权限控制依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!--导入连接MySQL的依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.tongchenggo.TongchenggoApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2. Write the entity class user, role.permission and the constant class constant.

User

package com.tongchenggo.entity;

import org.springframework.stereotype.Component;
import java.io.Serializable;
/*
* 用户类
* @author jiang
* @date 2021.6.11
* */
@Component
public class User implements Serializable {
    private Integer ID;
    private String userName;
    private String password;
    private Integer rid;
    private Role role;

    public Integer getRid() {
        return rid;
    }

    public void setRid(Integer rid) {
        this.rid = rid;
    }

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }

    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;
    }
}

Role

package com.tongchenggo.entity;

import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;

/*
* 角色类
* @author jiang
* @date 2021.6.11
* */
@Component
public class Role implements Serializable {
    private Integer ID;
    private String role;
    private List<Permission> permissionList;

    public List<Permission> getPermissionList() {
        return permissionList;
    }

    public void setPermissionList(List<Permission> permissionList) {
        this.permissionList = permissionList;
    }

    public Integer getID() {
        return ID;
    }

    public void setID(Integer ID) {
        this.ID = ID;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

}

Permission

package com.tongchenggo.entity;

import org.springframework.stereotype.Component;
import java.io.Serializable;

/*
* 权限类
* @author jiang
* @date 2021.6.11
* */
@Component
public class Permission implements Serializable {
    private Integer ID;
    private String permission;
    public Integer getID() {
        return ID;
    }

    public void setID(Integer ID) {
        this.ID = ID;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

}

Constant

package com.tongchenggo.constant;

/*
* 常量类
* @author jiang
* @date 2021.6.11
* */
public class Constant {
    //失败返回码
    public static final Integer FAIL_CODE = 0;
    //成功返回码
    public static final Integer SUCCESS_CODE = 1;
    //token失效返回码
    public static final Integer TOKEN_FAILURE_CODE = -1;
    //token加密密钥
    public static final String SECRET="jiang";

}

3. Create a database and add user, role, permission tables and associated tables.

Fourth, realize the data operation interface UserMapper, RoleMapper, PermissionMapper.

UserMapper

package com.tongchenggo.mapper;

import com.tongchenggo.entity.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

/*
* 用户表操作接口
* @author jiang
* @date 2021.6.11
* */
@Repository
public interface UserMapper {

    @Results(id = "userResultMap",value = {
            @Result(column = "uid",property = "ID" ,id = true),
            @Result(column = "userName" ,property = "userName"),
            @Result(column = "password",property = "password")

    })

    //通过用户名查询用户、密码
    @Select("select * from sys_user u where u.userName=#{userName}")
    User getUserByName(String userName);

    @Results(id="userInfoResultMap" , value = {
            @Result(column = "uid",property = "ID" ,id = true),
            @Result(column = "userName" ,property = "userName"),
            @Result(column = "password",property = "password"),
            @Result(column = "rid",property = "rid"),
            @Result(column = "rid",property ="role", one=@One(select = "com.tongchenggo.mapper.RoleMapper.getRoleByrid") ),

    })
    //通过用户名查询用户、角色
    @Select("select * from sys_user u where u.userName=#{userName}")
    User getUserRoleByName(String userName);

    //添加用户
    @Insert("insert into sys_user values(null,#{userName},#{password},#{rid})")
    void addUser(User user);
}

RoleMapper

package com.tongchenggo.mapper;

import com.tongchenggo.entity.Role;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

/*
* 角色表操作接口
* @author jiang
* @date 2021.6.11
* */
@Repository
public interface RoleMapper {
    @Results(id="roleResultMap" , value = {
            @Result(column = "rid",property = "ID" ,id = true),
            @Result(column = "role" ,property = "role"),
            @Result(column = "rid",property = "permissionList",many = @Many(select = "com.tongchenggo.mapper.PermissionMapper.getPermission"))
    })
    //角色ID查询用户角色
    @Select("select * from sys_role where sys_role.rid=#{rid} ")
    Role getRoleByrid(Integer rid);

}

PermissionMapper

package com.tongchenggo.mapper;

import com.tongchenggo.entity.Permission;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/*
* 权限表操作接口
* @author jiang
* @date 2021.6.11
* */
@Repository
public interface PermissionMapper {
    @Results(id = "permissionResultMap",value = {
            @Result(column = "pid",property = "ID",id = true),
            @Result(column = "permission",property = "permission")
    })
    //通过角色ID查询该角色拥有的权限
    @Select("select * from sys_permission p where p.pid in(select rp.pid from role_permission rp where rp.rid=#{rid})")
    List<Permission> getPermission(Integer rid);
}

5. Create a service service interface

UserService

package com.tongchenggo.service;

import com.tongchenggo.entity.User;
import com.tongchenggo.vo.ResultVO;

/*
* 用户相关服务接口
* @author jiang
* @date 2021.6.11
* */
public interface UserService {
    //登录
    ResultVO login(String userName, String password);
    //注册
    ResultVO register(String userName,String password);
    //退出
    ResultVO logout();
    //根据用户名获取用户
    User getUserByName(String userName);
}

6. Implementation of service interface

UserServiceImpl

package com.tongchenggo.serviceImpl;

import com.tongchenggo.constant.Constant;
import com.tongchenggo.utils.MD5Utils;
import com.tongchenggo.vo.ResultVO;
import com.tongchenggo.entity.User;
import com.tongchenggo.utils.JWTUtil;
import com.tongchenggo.mapper.UserMapper;
import com.tongchenggo.service.UserService;
import com.tongchenggo.vo.Token;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/*
* 用户相关服务接口实现
* @author jiang
* @date 2021.6.11
* */
@Service
public class UserServiceImpl implements UserService {
    public static Integer ROLE=1;
    @Autowired
    private UserMapper userMapper;
    /*
    * 用户登录
    * 返回登录状态,成功携带token,失败返回失败信息
    * */
    @Override
    public ResultVO login(String userName, String password) {
        //判断用户账号或密码是否为空
        if(userName==null|"".equals(userName)|password==null|"".equals(password)){
            return ResultVO.fail(Constant.FAIL_CODE,"账号或密码不能为空");
        }

        //根据用户名密码创建token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName,password);
        usernamePasswordToken.setRememberMe(false);
        //获取认证主体subject
        Subject subject = SecurityUtils.getSubject();
        try{
            //根据用户信息登录
            subject.login(usernamePasswordToken);
        }catch (Exception e){
            return ResultVO.fail(Constant.FAIL_CODE,"登录失败");
        }
        String token = null;
        try {
            token=JWTUtil.sign(userName);
        }catch (Exception e){
            e.getStackTrace();
        }
        return ResultVO.success("登录成功",new Token(token));
    }
    /*
    * 用户注册
    * 返回注册状态
    * */
    @Override
    public ResultVO register(String userName, String password) {
        //判断用户账号、密码、角色、权限是否为空
        if(userName==null|"".equals(userName)|password==null|"".equals(password)){
            return ResultVO.fail(Constant.FAIL_CODE,"账号、密码、角色、权限任一一项不可为空");
        }
        //根据用户名查询用户判断是否存在该用户
        User user = userMapper.getUserByName(userName);
        if(user==null){
            user = new User();
            user.setUserName(userName);
            user.setPassword(MD5Utils.encryptionPassword(password,userName));
            user.setRid(ROLE);
            try {
                userMapper.addUser(user);//添加用户
            }catch (Exception e){
                return ResultVO.fail(Constant.FAIL_CODE,"注册失败");
            }
            return ResultVO.success("注册成功");
        }else {
            return ResultVO.fail(Constant.FAIL_CODE,"用户已存在");
        }
    }
    /*
    * 用户退出
    * 返回退出结果
    * */
    public ResultVO logout(){
        SecurityUtils.getSubject().logout();
        return ResultVO.success("退出成功!");
    }
    /*
    * 根据用户名获取用户
    * */
    public User getUserByName(String userName){
        return userMapper.getUserRoleByName(userName);
    }
}

7. Write shiro-related configuration and custom implementation classes and JWT-related tools.

7.1, shiro configuration class

ShiroConfig

package com.tongchenggo.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/*
* shiro配置类
* @author jiang
* @date 2021.6.11
* */
@Configuration
public class ShiroConfig {
    /*
     * 配置密码匹配器
     * */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(10);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }
    /*
     * 配置自定义realm,设置密码匹配器
     * */
    @Bean
    public PasswordRealm passwordRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
        PasswordRealm passwordRealm = new PasswordRealm();
        passwordRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return passwordRealm;
    }
    @Bean
    public JwtRealm jwtRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
        JwtRealm jwtRealm = new JwtRealm();
        jwtRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return jwtRealm;
    }

    /*
     * 配置安全管理器,设置自定义realm,禁用session
     */
    @Bean
    public SecurityManager securityManager(PasswordRealm passwordRealm, JwtRealm jwtRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        Collection reams = new ArrayList();
        reams.add(passwordRealm);
        reams.add(jwtRealm);
        securityManager.setRealms(reams);
        //禁用session
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(defaultSubjectDAO);
        return  securityManager;
    }
    /*
     * @author jiang
     * @date 2021.6.11
     *
     * 配置shiro拦截器
     * shiro内置过滤器,可以实现权限相关拦截
     * 常用过滤器:
     * anon:无需认证,即无需登录就可以访问
     * authc:必须认证才可以访问
     * user:如果使用rememberMe的功能才可以直接访问
     * perms:必须拥有perms[xx]权限才可以访问
     * roles:必须拥有roles[xx]权限才可以访问
     * */

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //添加自定义拦截器filter
        shiroFilterFactoryBean.getFilters().put("jwt", new JwtFilter());
        //身份认证失败跳转到登录页面
        //该方法需要使用authc才会生效,这里使用使用自定义拦截器jwt验证身份,所以该方法不会生效
        shiroFilterFactoryBean.setLoginUrl("/error/unlogin");
        //身份认证成功跳转
        //前后分离项目中前端会用自己的逻辑在登录成功后进行跳转
//        shiroFilterFactoryBean.setSuccessUrl("/ok");
        //未授权页面
        //该方法需要roles或者perms拦截器才会生效,若使用自定义拦截器依然不会生效
        shiroFilterFactoryBean.setUnauthorizedUrl("/error/unauthorized");
        //创建拦截链集合,LinkedHashMap有序集合
        Map<String,String> filterMap = new LinkedHashMap<String,String>();
        //定义拦截规则
        //anon无需认证,jwt为自定义拦截器
        filterMap.put("/user/add/**","anon");
        filterMap.put("/user/post/**","anon");
        filterMap.put("/user/get01","jwt");
        filterMap.put("/user/get02","jwt,perms[add]");
        filterMap.put("/user/get03","jwt,roles[admin]");
        filterMap.put("/user/get04","jwt,roles[admin],perms[delete]");
        filterMap.put("/user/get05","jwt,roles[ordinary]");
        filterMap.put("/user/get06","jwt,roles[ordinary],perms[add]");
        filterMap.put("/user/get07","jwt,roles[ordinary],perms[find]");
        filterMap.put("/user/**", "jwt");
        //注入拦截链到拦截器
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //注入安全管理器到拦截器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }
}

When configuring the security manager, it is necessary to disable the session to prevent caching of user information.

When configuring an interceptor, you need to add a custom interceptor to the interceptor factory to implement your own identity authentication process. Shiro's default interceptors are commonly used:

Authentication: authc, user, anon.

Authorization: roles, perms.

When configuring interception rules, you first need to configure custom authentication interceptors + authorization interceptors, separated by commas ",", such as

filterMap.put("/user/get03","jwt,roles[admin]");

If you do not add jwt (the alias taken when adding the interceptor earlier), the authc filter will be used by default during authorization and authentication, and the user identity will be cached after the first login. Even if no token or a wrong token is carried during subsequent authentication Access will also pass identity authentication directly; after adding jwt, after the first user password login, the token will be returned to the user. When the user accesses the service, the token carried first needs to pass through the custom identity authentication process, and then the authorization authentication process is finally confirmed. Whether the user has permission to access the resource.

7.2. Customize JwtToken to implement AuthenticationToken.

JwtToken

package com.tongchenggo.shiro;

import org.apache.shiro.authc.AuthenticationToken;


/*
* 自定义token
* @author jiang
* @date 2021.6.11
* */
public class JwtToken implements AuthenticationToken {
    private String token;
    public JwtToken(String token) {
        this.token = token;
    }
    //将token作为返回值
    @Override
    public String getPrincipal() {
        return this.token;
    }

    @Override
    public String getCredentials() {
        return this.token;
    }
}

7.3. Custom realm. Here, two custom realm classes are rewritten to implement different verification methods (password verification and token verification).

PasswordRealm

package com.tongchenggo.shiro;

import com.tongchenggo.exception.MyShiroException;
import com.tongchenggo.entity.User;
import com.tongchenggo.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;


/*
* 密码验证类
* @author jiang
* @date 2021.6.11
* */
public class PasswordRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    /*
    *subject.login(token)方法中的token是UsernamePasswordToken时,调用此Realm的doGetAuthenticationInfo
    * 必须重写此方法
    *  */
    @Override
    public boolean supports(AuthenticationToken token) {
        return  token instanceof UsernamePasswordToken;
    }

    //用户认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("PasswordRealm执行");
                //通过authenticationToken获取用户名
                String userName = (String) authenticationToken.getPrincipal();
                //根据用户名从数据库查询用户信息
                User user = userService.getUserByName(userName);
                //判断用户是否存在
                if (user != null) {
                    //认证用户信息
                    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "passwordRealm");
                    //设置盐值加密
                    authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
                    return authenticationInfo;
                } else {
                    throw new MyShiroException("用户不存在");
                }

    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

}

JwtRealm

package com.tongchenggo.shiro;

import com.tongchenggo.entity.Permission;
import com.tongchenggo.entity.User;
import com.tongchenggo.exception.MyShiroException;
import com.tongchenggo.service.UserService;
import com.tongchenggo.utils.JWTUtil;
import com.tongchenggo.constant.Constant;
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.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;

/*
* token校验类
* @author jiang
* @date 2021.6.11
* */
public class JwtRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    /*
     *subject.login(token)方法中的token是JwtToken时,调用此Realm的doGetAuthenticationInfo
     * 必须重写此方法
     *  */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token!=null && token instanceof JwtToken;
    }
    /*
    * 校验token身份认证
    * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("JWTRealm执行");
            String token =(String)authenticationToken.getPrincipal();
            String userName = JWTUtil.getUsername(token);
            if(!JWTUtil.verify(token,userName)){
                throw new MyShiroException(Constant.TOKEN_FAILURE_CODE, "token无效,请重新登录");
            }else {
                //根据用户名从数据库查询用户信息
                User user = userService.getUserByName(userName);
                //判断用户是否存在
                if(user!=null){
                    //返回给客户端的token未加密,在shiro里比对时会根据规则加密后比对
                    //用户密码登录会根据用户输入密码加密后与数据库里加密过的密码对比
                    SimpleHash encryptionToken = new SimpleHash("MD5",
                            token, ByteSource.Util.bytes(userName),
                            10);
                    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,encryptionToken,ByteSource.Util.bytes(userName),"jwtRealm");

                    return authenticationInfo;
                }else{
                    throw new MyShiroException("token非法");
                }
            }
    }
    /*
    * 授权
    * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = userService.getUserByName(principalCollection.getPrimaryPrincipal().toString());
        List<String> perms = new ArrayList<String>();
        if(!CollectionUtils.isEmpty(user.getRole().getPermissionList())){
            for(Permission permission : user.getRole().getPermissionList()){
                perms.add(permission.getPermission());
            }
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRole(user.getRole().getRole());
        simpleAuthorizationInfo.addStringPermissions(perms);
        return simpleAuthorizationInfo;
    }

}

When rewriting the realm class, you must rewrite the supports method. In a multi-realm environment or when implementing AuthenticationToken by yourself, you need to compare and call the identity authentication and authorization methods in the corresponding realm according to the incoming token type.

7.4. Custom interceptor JwtFilter inherits BasicHttpAuthenticationFilter

package com.tongchenggo.shiro;


import com.tongchenggo.constant.Constant;
import com.tongchenggo.vo.ResultVO;
import com.tongchenggo.utils.ShiroSendMessageUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/*
* @author jiang
* @date 2021.6.11
* 自定义身份认证拦截器
* 认证流程:
* OncePerRequestFilter#doFilter=>AdviceFilter#doFilterInternal=>PathMatchingFilter#preHandle=>AccessControlFilter#onPreHandle
* AccessControlFilter#onPreHandle会调用isAccessAllowed和onAccessDenied方法来确认是否通过认证
* public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
*       return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
*   }
* */
public class JwtFilter extends BasicHttpAuthenticationFilter {
    /*
    * 重写createToken方法
    * 创建自定义的JwtToken
    * */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        // 获取请求头Authorization的值
        String authorization = getAuthzHeader(request);
        return new JwtToken(authorization);
    }

    /*
     * 重写isLoginAttempt方法
     * 判断用户是否想要登录。
     * 检测header里面是否包含Authorization字段,即token
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader("Authorization");
        return authorization != null;
    }

    /*
     * 重写executeLogin方法
     * 执行登录操作
     * 登录异常里发送自定义异常信息给用户
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = this.createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        } else {
            try {
                Subject subject = this.getSubject(request, response);
                subject.login(token);
                return this.onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException e) {
                ResultVO resultVO = ResultVO.fail("用户认证失败,请确认是否登录或登录已超时");
                ShiroSendMessageUtils.sendResponse(response, resultVO);
                return this.onLoginFailure(token, e, request, response);
            }
        }
    }

    /*
     * 重写onAccessDenied
     * isAccessAllowed身份认证未通过,执行此方法
     * 返回true,请求一定会通过
     * 返回false,结束过滤链
     * 这里直接返回false处理错误提示
     *
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        String authorization = getAuthzHeader(request);
        if (StringUtils.isEmpty(authorization)){
            ShiroSendMessageUtils.sendResponse(response, ResultVO.fail(Constant.TOKEN_FAILURE_CODE,"请求未携带token,请确认是否登录!"));

        }
        return false;
    }
    /*
    * 重写isAccessAllowed方法
    * 是否允许访问
    * 检查未携带token拒绝访问,在onAccessDenied方法里发送无token信息给用户,有token调用登录请求
    * */

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        boolean loggedIn=false;
        if (isLoginAttempt(request, response)) {
            try {
                loggedIn = executeLogin(request, response);
            } catch (Exception e) {
                loggedIn = false;
            }
        }
        return loggedIn;
    }



//        @Override
//    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
//        HttpServletRequest req =(HttpServletRequest)request;
//        HttpServletResponse res = (HttpServletResponse)response;
//        res.setHeader("Access-control-Allow-Origin",req.getHeader("Origin"));
//        res.setHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS,PUT,DELETE");
//        res.setHeader("Access-Control-Allow-Headers",req.getHeader("Access-Control-Request-Headers"));
//        if(req.getMethod().equals(RequestMethod.OPTIONS.name())){
//            res.setStatus(HttpStatus.OK.value());
//            return false;
//        }
//        return super.preHandle(request, response);
//    }
}

In the custom interceptor, the createToken method must be rewritten to return our custom JwtToken, so that when users access resources that require authorization, the JwtToken carried by the user can be obtained when using the custom interceptor JwtFilter, and the token can be verified by JwtRealm to confirm authority.

During the identity authentication process, Shiro will call isAccessAllowed and onAccessDenied through the onPreHandle() method to confirm whether the authentication is passed. One of the methods returns true to pass the identity authentication. We confirm whether the identity authentication is passed in the isAccessAllowed method, and return false directly in onAccessDenied , and return some authentication failed information to the user.

7.5. Write a custom exception class.

MyException

package com.tongchenggo.exception;

/*
* 自定义异常
* @author jiang
* @date 2021.6.11
* */

public class MyException extends RuntimeException {
    private Integer code = 0;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public MyException(String message) {
        super(message);
    }

    public MyException(Integer code, String message) {
        super(message);
        this.code = code;
    }

}

MyShiroException

package com.tongchenggo.exception;

import org.apache.shiro.authc.AuthenticationException;
/*
* 自定义shiro相关异常
* @author jiang
* @date 2021.6.11
* */
public class MyShiroException extends AuthenticationException {
    private Integer code=0;
    public MyShiroException(String message) {
        super(message);
    }

    public MyShiroException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

7.6. JWTUtil, a tool for generating tokens, MD5Utils for password encryption, and ShiroSendMessageUtils for sending abnormal information

JWTUtil

package com.tongchenggo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tongchenggo.constant.Constant;
import java.io.UnsupportedEncodingException;
import java.util.Date;

/*
* token生成、验证工具类
* @author jiang
* @date 2021.6.11
* */
public class JWTUtil {
    //有效期
    private static final long EXPIRE_TIME = 60*1000;
    //密钥
    private static final String SECRET = Constant.SECRET;

    /*
     * 校验token是否正确
     */
    public static boolean verify(String token, String username) {
        try {
            //根据密钥生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
            //效验token
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }


        /*
         * 获得token中的用户名
         */
        public static String getUsername(String token) {
            try {
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("username").asString();
            } catch (JWTDecodeException e) {
                return null;
            }
        }

        /*
         * 生成签名
         */
         public static String sign(String username) throws UnsupportedEncodingException {
             Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
             Algorithm algorithm = Algorithm.HMAC256(SECRET);
             Date date1 = new Date();
             return JWT.create()
                     .withIssuer("auth0")//签发人
                     .withClaim("username", username)//载体数据
                     .withIssuedAt(date1)//签发时间
                     .withExpiresAt(date)//过期时间
                     .sign(algorithm);//生成新的JWT
         }
}

MD5Utils

package com.tongchenggo.utils;

import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/*
* 密码加密类
* @author jiang
* @date 2021.6.11
* */
public class MD5Utils {
    public static String encryptionPassword(String password,String salt){
        return new SimpleHash("MD5",
                password, ByteSource.Util.bytes(salt),
                10).toString();
    }
}
ShiroSendMessageUtils
package com.tongchenggo.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tongchenggo.vo.ResultVO;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/*
* Shiro发送异常信息工具类
* @author jiang
* @date 2021.6.11
* */
public class ShiroSendMessageUtils {
    public static ObjectMapper objectMapper = new ObjectMapper();
    /*
     * response发送响应数据ResultVO
     */
    public static void sendResponse(ServletResponse response, ResultVO resultVO) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try (PrintWriter out = response.getWriter()){
            out.print(objectMapper.writeValueAsString(resultVO));
        }catch (Exception e){
            throw e;
        }
    }

}

8. Unified response result class

ResultVO

package com.tongchenggo.vo;


import com.tongchenggo.constant.Constant;
import java.io.Serializable;

/*
* 数据响应封装类
* @author jiang
* @date 2021.6.11
* */
public class ResultVO<T> implements Serializable {
    //响应状态
    private int status;
    //响应信息描述
    private String message;
    //响应数据
    private T data;

    public ResultVO(int status, String message){
        this.status = status;
        this.message = message;
    }
    public ResultVO(int status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static<T> ResultVO<T> success(String message,T data){
        return new ResultVO(Constant.SUCCESS_CODE,message,data);
    }
    public static<T> ResultVO<T> success(String message){
        return new ResultVO(Constant.SUCCESS_CODE,message);
    }

    public static ResultVO fail(String message){
        return new ResultVO(Constant.FAIL_CODE, message, null);
    }

    public static ResultVO fail(int status, String message){
        return new ResultVO(status, message, null);
    }


}

Token

package com.tongchenggo.vo;

import lombok.Data;

/*
* @author jiang
* @date 2021.6.11
* */
@Data
public class Token {
    private String token;
    public Token(String token){
        this.token = token;
    }
}

Nine, access controller writing

UserController

package com.tongchenggo.controller;

import com.tongchenggo.vo.ResultVO;
import com.tongchenggo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/*
* 用户注册、登录
* 测试权限控制
* @author jiang
* @date 2021.6.11
* */
@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/add/{userName}/{password}")
    public ResultVO register(@PathVariable String userName,@PathVariable String password){
        return userService.register(userName,password);
    }
    @RequestMapping("/post/{userName}/{password}")
    public ResultVO login(@PathVariable String userName,@PathVariable String password){
        return userService.login(userName,password);
    }
    @RequestMapping("/get07")
    public ResultVO get07(){
        return  ResultVO.success("已认证普通用户,有查询权限");
    }
    @RequestMapping("/get06")
    public ResultVO get06(){
        return  ResultVO.success("已认证,身份为管理员或普通用户,且需有添加权限才可访问");
    }
    @RequestMapping("/get05")
    public ResultVO get05(){
        return  ResultVO.success("已认证普通用户");
    }
    @RequestMapping("/get04")
    public ResultVO get04(){
        return  ResultVO.success("已认证且是管理员角色拥有删除权限");
    }
    @RequestMapping("/get03")
    public ResultVO get03(){
        return  ResultVO.success("已认证且是管理员角色");
    }
    @RequestMapping("/get02")
    public ResultVO get02(){
        return  ResultVO.success("已认证用户且有添加权限");
    }
    @RequestMapping("/get01")
    public ResultVO get01(){
        return  ResultVO.success("已认证用户");
    }

    @RequestMapping("/logout")
    public ResultVO logout(){
       return userService.logout();
    }

}

ErrorController

package com.tongchenggo.controller;

import com.tongchenggo.constant.Constant;
import com.tongchenggo.vo.ResultVO;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/*
* shiro身份未认证和未授权转发控制器
* @author jiang
* @date 2021.6.11
* */
@RestController
@RequestMapping("error")
public class ErrorController {
    @RequestMapping("/unauthorized")
    public ResultVO unauthorized(){
      return ResultVO.fail(Constant.FAIL_CODE,"无访问权限");
    }
    @RequestMapping("/unlogin")
    public ResultVO unlogin(){
      return ResultVO.fail(Constant.FAIL_CODE,"未登录用户");
    }
}

Ten, postman test.

10.1. Log in as user aa.

Guess you like

Origin blog.csdn.net/weixin_44341110/article/details/118492297