Spring boot integrates Shiro and JWT


reference resources:

It is planned to realize the permission control of adding, deleting, modifying and querying levels

1. Table structure

Menu table

  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) COLLATE utf8mb4_ unicode_ Ci not null comment 'menu name',
  `parent_ ID ` bigint (20) not null comment 'parent node ID',
  `url` varchar(256) COLLATE utf8mb4_ unicode_ Ci not null comment 'path',
  `sort_ ID ` int (11) default '0' comment 'sort ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_ INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ unicode_ Ci comment = 'menu table';

Menu – operation table

CREATE TABLE `menu_opt` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `menu_ ID ` bigint (20) not null comment 'menu ID',
  `opt_ ID ` bigint (20) not null comment 'operation ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_ INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ unicode_ Ci comment = 'menu - operation association table';

Operation table

  `opt_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `opt_ name` varchar(32) COLLATE utf8mb4_ unicode_ Ci default null comment 'operation name',
  PRIMARY KEY (`opt_id`)
) ENGINE=InnoDB AUTO_ INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ unicode_ Ci comment = 'operation table';

Role table

  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(256) COLLATE utf8mb4_ unicode_ Ci default null comment 'role name',
  PRIMARY KEY (`id`)

Role menu operation table

CREATE TABLE `role_menu_opt` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_ ID ` bigint (20) not null comment 'role ID',
  `menu_ ID ` bigint (20) not null comment 'menu ID',
  `opt_ ID ` bigint (20) not null comment 'operation ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_ INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ unicode_ Ci comment = 'role menu operation association table';

User table

CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) NOT NULL,
  `pwd` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)

User role table

CREATE TABLE `user_role` (
  `user_ ID ` bigint (20) not null comment 'user ID',
  `role_ ID ` bigint (20) not null comment 'role ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_ INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ unicode_ Ci comment = 'role table';

2. Use mybatis plus to automatically generate some classes

Refer to:https://www.jianshu.com/p/c984ac0b67e8

3. Add Shiro and JWT dependencies



4. Shiro and JWT configuration

  • shiro config
public class ShiroConfig {

    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //Associate defaultwebsecuritymanager

        //Add Shiro built-in filter
         *Anon: access without authentication
         *Auth: authentication is required to access
         *User: you must have the remember me function to use it
         *Perms: you can only access a resource with permission
         *Role: you must have a role permission to access
         * */
        //Add filter
        Map<String, Filter> filters = new HashMap<>(4);
        //Custom authentication authorization filter
        filters.put("auth", new AuthFilter());
        //Add a custom authentication authorization filter

        //Put the path to be intercepted in the map
        Map<String, String> filterMap = new LinkedHashMap<>();
        //Release login interface
        filterMap.put("/login", "anon");
        //Release logout interface
        filterMap.put("/logout", "anon");
        //Intercept all paths, and it will automatically run to the user-defined filter authfilter
        filterMap.put("/**", "auth");
        return shiroFilterFactoryBean;

    public DefaultWebSecurityManager defaultWebSecurityManager(AuthRealm authRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        //Associated realm

         *Close Shiro's own session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        return defaultWebSecurityManager;

    //Inject custom realm into defaultwebsecuritymanager
    public AuthRealm authRealm() {
        return new AuthRealm();

    //By calling initializable Init() and destroyable Destroy () method to manage the Shiro bean life cycle
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();

    //Open Shiro permission annotation
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        return advisor;
  • realm
public class AuthRealm extends AuthorizingRealm {

    private SysUserMapper sysUserMapper;

    private UserRoleMapper userRoleMapper;

    private RoleMenuOptMapper roleMenuOptMapper;

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //Get the token from the front end
        String accessToken = (String) token.getPrincipal();
        //Search the user name in the cache according to the token
        String userId = JwtUtils.getAudience(accessToken);
        if (userId == null) {
            //If the user name searched is empty, the token is invalid
            Throw new correctcredentialsexception ("token is invalid, please login again");
        JwtUtils.verifyToken(accessToken, userId);
        SysUser user = sysUserMapper.selectById(Long.valueOf(userId));
        if (user == null) {
            Throw new unknownaccountexception ("user does not exist!");
        //This method needs to return data of type authenticationinfo
        //Therefore, return its implementation class simpleauthenticationinfo, and pass in the user and the obtained token to realize automatic authentication

        return new SimpleAuthenticationInfo(user, accessToken, "");

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //Get the user object user from the authentication
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        //This method needs a return value of type authorizationinfo, so it returns its implementation class simpleauthorizationinfo
        //Set the user's permission through addstringpermission() in simpleauthorizationinfo
        QueryWrapper<UserRole> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", user.getId());
        List<UserRole> userRoles = userRoleMapper.selectList(queryWrapper);
        List<Long> roleIds = userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toList());
        QueryWrapper<RoleMenuOpt> optWrapper = new QueryWrapper<>();
        optWrapper.in("role_id", roleIds);
        List<RoleMenuOpt> roleMenuOpts = roleMenuOptMapper.selectList(optWrapper);
        List<String> optList = roleMenuOpts.stream().map(opt -> String.valueOf(opt.getOptId()))
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        return simpleAuthorizationInfo;

//    @Override
//    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
//// custom password matcher
//        MyCredentialsMatcher currentCredentialsMatcher = new MyCredentialsMatcher();
//        super.setCredentialsMatcher(currentCredentialsMatcher);
//    }
  • shiro filter
public class AuthFilter extends AuthenticatingFilter {

    //Generate custom token
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        //Get token from header
        String token = httpServletRequest.getHeader("token");
        return new JwtToken(token);

    //All requests denied access
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //Allow option requests to pass
        return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());

    //If the access request is denied, the onaccessdenied method obtains the token first, and then calls the executelogin method
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        //Get request token
        String token = httpServletRequest.getHeader("token");
        // StringUtils. Isblank (string STR) determines whether the str string is empty or the length is 0
        if (token == null || "".equals(token)) {
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
            Response msg = Response. Fail ("please login first");
            return false;
        return executeLogin(request, response);

    //Called when the token fails
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        try {
            //Exception handling login failure
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            Response msg = Response. Fail ("login certificate has expired, please login again");
        } catch (IOException e1) {

        return false;

  • jwt util
public class JwtUtils {

     *Issuing object: the ID of this user
     *Issued on: now
     *Effective time: 30 minutes
     *Load content: temporarily designed as: this person's name, this person's nickname
     *Encryption key: this person's ID plus a string
    public static String createToken(String userId, String realName, String userName) {

        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE, 30);
        Date expiresDate = nowTime.getTime();
        return JWT.create()
                //Issuing object
                //Release time
                .withIssuedAt(new Date())
                //Effective time
                //Load, just write a few
                .withClaim("userName", userName)
                .withClaim("realName", realName)
                .sign(Algorithm.HMAC256(userId + "HelloLehr"));

     *Verify the legitimacy. The secret parameter should be the user ID
     * @param token
    public static void verifyToken(String token, String secret) {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret + "HelloLehr")).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
            //Validation failure
            //The exception thrown here is one of my custom exceptions. You can write it in other words
            throw new TokenUnavailableException();

     *Get issuing object
    public static String getAudience(String token) {
        String audience = null;
        try {
            audience = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            //Here is the token parsing failure
            throw new TokenUnavailableException();
        return audience;

     *Get the value of the load by the load name
    public static Claim getClaimByName(String token, String name) {
        return JWT.decode(token).getClaim(name);


jwt token

public class JwtToken extends UsernamePasswordToken {

    String token;

    public JwtToken(String token) {
        this.token = token;

    public Object getPrincipal() {
        return token;

    public Object getCredentials() {
        return token;

With the above classes, the calculation is basically implemented. Next, write login

5. Login

login controller

public class LoginController {

    private SysUserMapper sysUserMapper;

    public Response login(@RequestParam("username") String username, @RequestParam("password") String password) {
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
        if (sysUser == null) {
            return Response. Fail ("account or password error");
        if (!sysUser.getPwd().equals(password)) {
            return Response. Fail ("account or password error");
        String token = JwtUtils.createToken(sysUser.getId().toString(), sysUser.getUsername(), sysUser.getUsername());
        return Response. Success ("login succeeded") add("token", token);


6. Test controller

public class MenuController {

    private MenuService menuService;

    public Response view() {
        return Response.success("success").add("menu", menuService.list());

    public Response add() {
        log.info("menu add.....");
        return Response.success("success");