SpringBoot+SpringSecurity+RBAC+JWT实现动态权限框架

一、创建数据库表
DROP TABLE IF EXISTS luo_admin;
CREATE TABLE luo_admin (
id bigint(20) NOT NULL AUTO_INCREMENT,
username varchar(64) DEFAULT NULL,
password varchar(64) DEFAULT NULL,
icon varchar(500) DEFAULT NULL COMMENT ‘头像’,
email varchar(100) DEFAULT NULL COMMENT ‘邮箱’,
nick_name varchar(200) DEFAULT NULL COMMENT ‘昵称’,
note varchar(500) DEFAULT NULL COMMENT ‘备注信息’,
create_time datetime DEFAULT NULL COMMENT ‘创建时间’,
login_time datetime DEFAULT NULL COMMENT ‘最后登录时间’,
status int(1) DEFAULT ‘1’ COMMENT ‘帐号启用状态:0->禁用;1->启用’,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT=‘后台用户表’;
添加数据:
INSERT INTO luo_admin VALUES (3, ‘admin’, ‘$2a 10 10 10TyteFqX1w78EDX81d4wr6.pJEYm9b9BdjJ1QF/U./ZEz9RuubWOE2’, ‘http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20190129/170157_yIl3_1767531.jpg’, ‘admin@163.com’, ‘系统管理员’, ‘系统管理员’, ‘2018-10-08 13:32:47’, ‘2022-06-21 10:35:39’, 1);
INSERT INTO luo_admin VALUES (10, ‘Test’, ‘$2a 10 10 10amtbA4JqnEshTdTKBAYPzumwDu4PMlbnulYPjGncfra2wXs92ao2K’, NULL, ‘testqq.com’, ‘测试账号’, ‘111’, ‘2022-06-21 10:30:21’, ‘2022-06-21 10:32:50’, 1);
DROP TABLE IF EXISTS luo_admin_permission_relation;
CREATE TABLE luo_admin_permission_relation (
id bigint(20) NOT NULL AUTO_INCREMENT,
admin_id bigint(20) DEFAULT NULL,
permission_id bigint(20) DEFAULT NULL,
type int(1) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT=‘后台用户和权限关系表(除角色中定义的权限以外的加减权限)’;
INSERT INTO luo_admin_permission_relation VALUES (1, 3, 16, -1);
INSERT INTO luo_admin_permission_relation VALUES (2, 3, 17, -1);
INSERT INTO luo_admin_permission_relation VALUES (3, 3, 18, -1);
INSERT INTO luo_admin_permission_relation VALUES (4, 3, 19, -1);
INSERT INTO luo_admin_permission_relation VALUES (5, 3, 20, -1);
INSERT INTO luo_admin_permission_relation VALUES (6, 3, 21, -1);
CREATE TABLE luo_admin_role_relation (
id bigint(20) NOT NULL AUTO_INCREMENT,
admin_id bigint(20) DEFAULT NULL,
role_id bigint(20) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COMMENT=‘后台用户和角色关系表’;
INSERT INTO luo_admin_role_relation VALUES (13, 3, 1);
INSERT INTO luo_admin_role_relation VALUES (21, 3, 5);
INSERT INTO luo_admin_role_relation VALUES (22, 10, 5);
CREATE TABLE luo_permission (
id bigint(20) NOT NULL AUTO_INCREMENT,
pid bigint(20) DEFAULT NULL COMMENT ‘父级权限id’,
name varchar(100) DEFAULT NULL COMMENT ‘名称’,
value varchar(200) DEFAULT NULL COMMENT ‘权限值’,
component varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT ‘组件名’,
type int(1) DEFAULT NULL COMMENT ‘权限类型:0->目录;1->菜单;2->按钮(接口绑定权限)’,
uri varchar(200) DEFAULT NULL COMMENT ‘前端资源路径’,
title varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT ‘标题’,
create_time datetime DEFAULT NULL COMMENT ‘创建时间’,
status int(1) DEFAULT NULL COMMENT ‘启用状态;0->禁用;1->启用’,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8 COMMENT=‘后台用户权限表’;
INSERT INTO luo_permission VALUES (1, 0, ‘Layout’, ‘pms:medicine:redad’, ‘Layout’, NULL, ‘/’, ‘Layout’, ‘2022-06-15 21:11:28’, 1);
INSERT INTO luo_permission VALUES (2, 1, ‘Home’, ‘pms:medicine:redad’, ‘Home’, NULL, ‘/home’, ‘首页’, ‘2022-06-15 21:11:31’, 1);
INSERT INTO luo_permission VALUES (3, 1, ‘BaseCompany’, ‘pms:medicine:redad’, ‘CompanyManage’, NULL, ‘/base/company’, ‘医药公司管理’, ‘2022-06-15 21:16:08’, 1);
INSERT INTO luo_permission VALUES (4, 1, ‘BaseSale’, ‘pms:medicine:redad’, ‘SaleManage’, NULL, ‘/base/sale’, ‘销售地点管理’, ‘2022-06-15 21:16:11’, 1);
INSERT INTO luo_permission VALUES (5, 1, ‘BaseCity’, ‘pms:medicine:redad’, ‘CityManage’, NULL, ‘/base/city’, ‘城市信息管理’, ‘2022-06-15 21:16:15’, 1);
INSERT INTO luo_permission VALUES (6, 1, ‘ManageDrug’, ‘pms:medicine:redad’, ‘DrugManage’, NULL, ‘/manage/drug’, ‘药品信息管理’, ‘2022-06-15 21:16:18’, 1);
INSERT INTO luo_permission VALUES (7, 1, ‘MedicalPolicy’, ‘pms:medicine:redad’, ‘MedicalPolicy’, NULL, ‘/manage/medical/policy’, ‘医保政策管理’, ‘2022-06-15 21:16:21’, 1);
INSERT INTO luo_permission VALUES (8, 1, ‘CompanyPolicy’, ‘pms:medicine:redad’, ‘CompanyPolicy’, NULL, ‘/manage/company/policy’, ‘医药公司政策管理’, ‘2022-06-15 21:16:24’, 1);
INSERT INTO luo_permission VALUES (9, 1, ‘DoctorManage’, ‘pms:medicine:redad’, ‘DoctorManage’, NULL, ‘/manage/doctor’, ‘医生信息管理’, ‘2022-06-15 21:16:28’, 1);
INSERT INTO luo_permission VALUES (10, 1, ‘MaterialManage’, ‘pms:medicine:redad’, ‘MaterialManage’, NULL, ‘/manage/material’, ‘必备材料管理’, ‘2022-06-15 21:16:32’, 1);
INSERT INTO luo_permission VALUES (12, 1, ‘luo’, ‘pms:medicine:redad’, ‘Layout’, NULL, ‘/admin’, ‘权限’, ‘2022-06-16 11:40:08’, 1);
INSERT INTO luo_permission VALUES (13, 12, ‘admin’, ‘pms:medicine:redad’, ‘admin’, NULL, ‘/luo/admin’, ‘用户列表’, ‘2022-06-16 17:16:44’, 1);
INSERT INTO luo_permission VALUES (14, 12, ‘role’, ‘pms:medicine:redad’, ‘role’, NULL, ‘/luo/role’, ‘角色列表’, ‘2022-06-16 17:18:44’, 1);
INSERT INTO luo_permission VALUES (15, 12, ‘allocMenu’, ‘pms:medicine:redad’, ‘allocMenu’, NULL, ‘/luo/allocMenu’, ‘菜单列表’, ‘2022-06-16 17:20:47’, 1);
INSERT INTO luo_permission VALUES (16, 0, ‘adminList’, ‘pms:medicine:redad’, ‘adminList’, NULL, ‘/api/admin/list’, ‘获取用户列表’, ‘2022-06-19 23:06:25’, 1);
INSERT INTO luo_permission VALUES (17, 0, ‘adminRole’, ‘pms:medicine:redad’, ‘adminRole’, NULL, ‘/api/role/listAll’, ‘获取角色列表’, ‘2022-06-19 23:23:11’, 1);
INSERT INTO luo_permission VALUES (18, 0, ‘adminRoleUpdate’, ‘pms:medicine:add’, ‘adminRoleUpdate’, NULL, ‘/api/admin/role/update’, ‘修改用户角色’, ‘2022-06-20 11:02:53’, 1);
INSERT INTO luo_permission VALUES (19, 0, ‘updateAdmin’, ‘pms:medicine:update’, ‘updateAdmin’, NULL, ‘/api/admin/update/v/’, ‘修改用户数据’, ‘2022-06-20 11:21:51’, 1);
INSERT INTO luo_permission VALUES (20, 0, ‘deleteAdmin’, ‘pms:medicine:delete’, ‘deleteAdmin’, NULL, ‘/api/admin/delete/v/’, ‘删除用户数据’, ‘2022-06-21 09:47:31’, 1);
INSERT INTO luo_permission VALUES (21, 0, ‘addAdmin’, ‘pms:medicine:add’, ‘addAdmin’, NULL, ‘/api/admin/register’, ‘添加用户数据’, ‘2022-06-21 10:13:45’, 1);
DROP TABLE IF EXISTS luo_role;
CREATE TABLE luo_role (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(100) DEFAULT NULL COMMENT ‘名称’,
description varchar(500) DEFAULT NULL COMMENT ‘描述’,
admin_count int(11) DEFAULT NULL COMMENT ‘后台用户数量’,
create_time datetime DEFAULT NULL COMMENT ‘创建时间’,
status int(1) DEFAULT ‘1’ COMMENT ‘启用状态:0->禁用;1->启用’,
sort int(11) DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT=‘后台用户角色表’;
INSERT INTO luo_role VALUES (1, ‘系统管理员’, ‘系统管理员’, 0, ‘2018-09-30 15:46:11’, 1, 0);
INSERT INTO luo_role VALUES (5, ‘医生’, ‘医生’, 0, ‘2022-06-20 10:50:48’, 1, 0);
DROP TABLE IF EXISTS luo_role_permission_relation;
CREATE TABLE luo_role_permission_relation (
id bigint(20) NOT NULL AUTO_INCREMENT,
role_id bigint(20) DEFAULT NULL,
permission_id bigint(20) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT=‘后台用户角色和权限关系表’;
INSERT INTO luo_role_permission_relation VALUES (1, 1, 1);
INSERT INTO luo_role_permission_relation VALUES (2, 1, 2);
INSERT INTO luo_role_permission_relation VALUES (3, 1, 3);
INSERT INTO luo_role_permission_relation VALUES (4, 1, 4);
INSERT INTO luo_role_permission_relation VALUES (5, 1, 5);
INSERT INTO luo_role_permission_relation VALUES (6, 1, 6);
INSERT INTO luo_role_permission_relation VALUES (7, 1, 7);
INSERT INTO luo_role_permission_relation VALUES (8, 1, 8);
INSERT INTO luo_role_permission_relation VALUES (18, 1, 9);
INSERT INTO luo_role_permission_relation VALUES (19, 1, 10);
INSERT INTO luo_role_permission_relation VALUES (21, 1, 11);
INSERT INTO luo_role_permission_relation VALUES (22, 1, 12);
INSERT INTO luo_role_permission_relation VALUES (23, 1, 13);
INSERT INTO luo_role_permission_relation VALUES (24, 1, 14);
INSERT INTO luo_role_permission_relation VALUES (25, 1, 15);
INSERT INTO luo_role_permission_relation VALUES (28, 5, 1);
INSERT INTO luo_role_permission_relation VALUES (29, 5, 2);
二、创建SpringSecurity配置类
1.白名单配置

@Component
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
    private List<String> urls = new ArrayList<>();

    public List<String> getUrls() {
        return urls;
    }

    public void setUrls(List<String> urls) {
        this.urls = urls;
    }
}
secure:
  ignored:
    urls: #安全路径白名单
      - /**/*.html
      - /**/*.js
      - /**/*.css
      - /**/*.png
      - /favicon.ico
      - /actuator/**
      - /login
      - /permissions
import com.lm.common.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Configuration
public class SecurityConfig {
    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private DynamicSecurityFilter dynamicSecurityFilter;
    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
                httpSecurity.authorizeRequests();
        //添加白名单,设置不需要拦截的内容
        for (String url : ignoreUrlsConfig.getUrls()) {
            registry.antMatchers(url).permitAll();
        }
        //允许跨域请求的OPTIONS请求
        registry.antMatchers(HttpMethod.OPTIONS,"/**")
                .permitAll();
        httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
                .disable()
                .cors().configurationSource(corsConfigurationSource()).and()
                .sessionManagement()// 基于token,所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                .authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        //配置JWT过滤器
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);

        //有动态权限配置时添加动态权限校验过滤器,当遇到被拦截的请求时会进入到这里验证
        registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
        //登录成功处理器

        return httpSecurity.build();
    }

    /**
     * 处理跨域问题
     * @return
     */
    public CorsConfigurationSource corsConfigurationSource() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowCredentials(true);
        configuration.addAllowedHeader("*");
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
        configuration.setMaxAge(Duration.ofHours(1));
        source.registerCorsConfiguration("/**", configuration);
        return source;

    }
}

package com.lm.common.utils;

import cn.hutool.core.collection.CollUtil;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Iterator;

/**
 * @author luoming
 * 动态权限决策管理器,用于判断用户是否有访问权限
 * @date 2022/6/12 11:32
 */
@Component
public class DynamicAccessDecisionManager implements AccessDecisionManager {
    /**
     *
     * @param authentication 当前正在请求受包含对象的Authentication
     * @param object 受保护对象,其可以是一个MethodInvocation、JoinPoint或FilterInvocation。
     * @param configAttributes 与正在请求的受保护对象相关联的配置属性
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        // 当接口未被配置资源时直接放行
        if (CollUtil.isEmpty(configAttributes)){
            return;
        }
        //将访问所需资源和用户拥有资源进行比较
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while (iterator.hasNext()){
            ConfigAttribute configAttribute = iterator.next();
            String needAuthority = configAttribute.getAttribute();
            for (GrantedAuthority authority : authentication.getAuthorities()) {
                if (needAuthority.trim().equals(authority.getAuthority())){
                    return;
                }
            }
        }
        throw  new AccessDeniedException("抱歉,您没有访问权限");
    }

    /**
     * 表示当前AccessDecisionManager是否支持对应的ConfigAttribute
     * @param attribute
     * @return
     */
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    /**
     * 表示当前AccessDecisionManager是否支持对应的受保护对象类型
     * @param clazz
     * @return
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}


package com.lm.common.utils;

import com.lm.config.IgnoreUrlsConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;

/**
 * @author luoming
 * 动态权限过滤器,用于实现基于路径的动态权限过滤
 * @date 2022/6/8 21:24
 */
@Component
public class DynamicSecurityFilter  extends AbstractSecurityInterceptor implements Filter {
    @Autowired
    private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        //FilterInvocation
        FilterInvocation fi = new FilterInvocation(servletRequest,servletResponse,filterChain);
        //如果是options请求就放行,
        //当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起 OPTIONS 请求,即 CORS 预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。
        if (request.getMethod().equals(HttpMethod.OPTIONS.toString())){
            fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
            return;
        }
        //白名单直接放行
        PathMatcher pathMatcher = new AntPathMatcher();
        List<String> urls = ignoreUrlsConfig.getUrls();
        for (String url : urls) {
            if (pathMatcher.match(url,request.getRequestURI())){
                fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
                return;
            }
        }
        //此处会调用AccessDecisionManager中的decide方法进行鉴权操作
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
        }catch (Exception e){
            super.afterInvocation(token,null);
        }
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return dynamicSecurityMetadataSource;
    }

    @Autowired
    public void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager){
        super.setAccessDecisionManager(dynamicAccessDecisionManager);
    }


}
package com.lm.common.utils;


import cn.hutool.core.util.URLUtil;
import com.lm.pojo.UmsAdmin;
import com.lm.pojo.UmsPermission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.util.*;

/**
 * @author luoming
 * 动态权限数据源,用于获取动态权限规则
 * @date 2022/6/8 21:27
 */
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

        List<ConfigAttribute> configAttributes = new ArrayList<>();
        //获取当前访问路径
        String url = ((FilterInvocation) object).getRequestUrl();
        //http://localhost:8080/search/name=zhangsan&age=20 完事后只保留/search
        //该方法用于获取URL链接中的path部分字符串
        String path = URLUtil.getPath(url);
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal1 = authentication.getPrincipal();
        UmsAdmin userDetails = null;
        if (principal1 instanceof UserDetails) {
            userDetails = (UmsAdmin) principal1;
            List<UmsPermission> permissions = userDetails.getPermissions();
            PathMatcher pathMatcher = new AntPathMatcher();
            for (UmsPermission umsPermission : permissions) {
                if (pathMatcher.match(umsPermission.getUri(), path)) {
                    SecurityConfig securityConfig = new SecurityConfig(umsPermission.getId() + ":" + umsPermission.getValue());
                    configAttributes.add(securityConfig);
                }

            }
        }

        if (configAttributes.isEmpty()){
            //没有匹配到资源,返回ROLE_LOGIN
            return SecurityConfig.createList("ROLE_LOGIN");
        }
        //未设置权限返回空集合
        return configAttributes;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}
package com.lm.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author luoming
 * JWT登录过滤器 确保一次请求只能通过一次当前过滤器
 * 当访问后台服务时,jWt过滤器负责拦截请求验证token
 * @date 2022/6/8 15:07
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtils jwtTokenUtil;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        //如果获取的token不为空,并且 头部是相同的就返回true
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            String userToken = authHeader.substring(this.tokenHead.length());
            String userName = jwtTokenUtil.getUserNameFromToken(userToken);
            log.info("checking userName:{}",userName);
            //判断数据库中是否有此用户,并且判断没有其他用户已登录
            if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null){
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName);
                //判断当前用户是否过期
                if (jwtTokenUtil.validateToken(userToken,userDetails)){
                    //验证密码
                    //用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    log.info("checking userName:{}",userName);
                    //如果用户存在且用户的密码相同,则在SecurityContextHolder.getContext().setAuthentication()放入authentication
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

                }
            }
        }
        filterChain.doFilter(request,response);
    }
}
package com.lm.common.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author luoming
 * jwt 生成工具
 * jwt token格式: header.payload.signature
 * header格式(算法、token的类型)
 * {"alg":"HS521":"JWT"}
 *  payload的格式(用户名、创建时间、生成时间):
 *  * {"sub":"wang","created":1489079981393,"exp":1489684781}
 *  * signature的生成算法:
 *  * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 * @date 2022/6/7 21:29
 */
@Component
@Slf4j
public class JwtTokenUtils {
    @Value("${jwt.expiration}")
    private Long expiration;
    @Value("${jwt.secret}")
    private String secret;
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    /**
     * 生成token
     * @param claims
     * @return
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从token中获取JWT中的负载
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token){
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            log.info("JWT格式验证失败:{}",token);
        }
        return claims;
    }

    public String getUserNameFromToken(String token){
        String userName;
        try {
            Claims claims = getClaimsFromToken(token);
            userName = claims.getSubject();
        }catch (Exception e){
            userName = null;
        }
        return userName;
    }
    /**
     * 生成token过期时间
     * @return
     */
    public Date generateExpirationDate(){
        return new Date(System.currentTimeMillis()+expiration*1000);
    }

    /**
     * 判断token是否过期
     * @param token
     * @return
     */
    public boolean isTokenExpired(String token){
        Date expiredDateFromToken = getExpiredDateFromToken(token);
        return expiredDateFromToken.before(new Date());
    }

    /**
     * 根据token获取过期时间
     * @param token
     * @return
     */
    public Date getExpiredDateFromToken(String token){
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 验证token是否有效
     * @param token
     * @param userDetails
     * @return
     */
    public boolean validateToken(String token, UserDetails userDetails){
        String userName = getUserNameFromToken(token);
        return userName.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 根据用户信息生成token
     * @param userDetails
     * @return
     */
    public String generateToken(UserDetails userDetails){
        Map<String,Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }

    /**
     * 判断是否可以刷新token
     * @param token
     * @return
     */
    public boolean canRefresh(String token){return isTokenExpired(token);}

    /**
     * 刷新token
     */
    public String refreshToken(String token){
        Claims claimsFromToken = getClaimsFromToken(token);
        claimsFromToken.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claimsFromToken);
    }
}
package com.lm.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lm.common.api.CommonResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 当未登录或者token失效访问接口时,自定义的返回结果
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        CommonResult<String> unauthorized = CommonResult.unauthorized(authException.getMessage());
        response.getWriter().println(JSONObject.toJSONString(unauthorized));
        response.getWriter().flush();
    }
}
package com.lm.common.utils;

import com.alibaba.fastjson.JSONObject;
import com.lm.common.api.CommonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 当访问接口没有权限时,自定义的返回结果
 * Created by macro on 2018/4/26.
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONObject.toJSONString(CommonResult.forbidden(e.getMessage())));
        response.getWriter().flush();
    }
}

设置跨域

package com.lm.common.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author luoming
 * @date 2022/6/14 23:44
 */
@Component
public class CorsFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        response.setHeader("Access-Control-Expose-Headers", "Location");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

权限实体:

public class UmsAdmin implements Serializable, UserDetails {
    private Long id;

    private String username;

    private String password;

    @ApiModelProperty(value = "头像")
    private String icon;

    @ApiModelProperty(value = "邮箱")
    private String email;

    @ApiModelProperty(value = "昵称")
    private String nickName;

    @ApiModelProperty(value = "备注信息")
    private String note;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "最后登录时间")
    private Date loginTime;

    @ApiModelProperty(value = "帐号启用状态:0->禁用;1->启用")
    private Integer status;

    private static final long serialVersionUID = 1L;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    @Override
    public String getUsername() {
        return username;
    }

    private List<UmsPermission> permissions;

    public List<UmsPermission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<UmsPermission> permissions) {
        this.permissions = permissions;
    }

    提示账号是否过期 true 末过期 false 过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    是否处于锁定状态
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    //指示用户凭证(密码) 是否过期 true 未过期 false过期
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        boolean enabled = true;
        if (this.status == 0){
            enabled = false;
        }
        return enabled;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        //返回当前用户的权限
        for (UmsPermission umsPermission : permissions) {
            authorities.add(new SimpleGrantedAuthority(umsPermission.getId() + ":" + umsPermission.getValue()));
        }
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }

mapper 层

@Mapper
public interface AdminMapper {
    public UmsAdmin loadAdminByUserName(String userName);

    public List<UmsRole> getUmsRole(Long id);
}


@Mapper
public interface UmsAdminRoleRelationMapper {

    /**
     * 获取用户所有权限(包括+-权限)
     */
    List<UmsPermission> getPermissionList(@Param("adminId") Long adminId);
}

service层

public interface AdminService {
    /**
     * 登录功能
     * @param userName 用户名
     * @param passWord 密码
     * @return 生成的JWT的token
     */

    public Map<String, Object> login(String userName,String passWord);

    /**
     * 获取用户所有权限(包括角色权限和+-权限)
     */
    List<UmsPermission> getPermissionList(Long adminId);


@Service
@Slf4j
public class AdminServiceImpl implements AdminService, UserDetailsService {
    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private JwtTokenUtils jwtTokenUtil;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UmsAdminRoleRelationMapper umsAdminRoleRelationMapper;
    @Autowired
    private UmsRoleMapper umsRoleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UmsAdmin umsAdmin = adminMapper.loadAdminByUserName(username);
        if (umsAdmin != null){
            List<UmsPermission> permissionList = umsAdminRoleRelationMapper.getPermissionList(umsAdmin.getId());
            umsAdmin.setPermissions(permissionList);
            return umsAdmin;
        }
        throw new UsernameNotFoundException("用户名或密码错误");

    }

    @Override
    public Map<String, Object> login(String userName, String passWord) {
        Map<String, Object> loginResult = new HashMap<>();
        String token = null;
        UserDetails userDetails = null;
        try {
             userDetails = loadUserByUsername(userName);
            if(userDetails==null){
                return loginResult;
            }
            if (!passwordEncoder.matches(passWord, userDetails.getPassword())) {
                throw new BadCredentialsException("密码不正确");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
        } catch (AuthenticationException e) {
            log.warn("登录异常:{}", e.getMessage());
        }
        loginResult.put("token",token);
        loginResult.put("userInfo",(UmsAdmin)userDetails);
        return loginResult;
    }

    @Override
    public List<UmsPermission> getPermissionList(Long adminId) {
        return umsAdminRoleRelationMapper.getPermissionList(adminId);
    }

}

mapper映射

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lm.mapper.UmsAdminRoleRelationMapper">
    <select id="getPermissionList" resultType="com.lm.pojo.UmsPermission">
        SELECT
            p.*
        FROM
            ums_admin_role_relation ar
                LEFT JOIN ums_role r ON ar.role_id = r.id
                LEFT JOIN ums_role_permission_relation rp ON r.id = rp.role_id
                LEFT JOIN ums_permission p ON rp.permission_id = p.id
        WHERE
            ar.admin_id = #{adminId}
          AND p.id IS NOT NULL
          AND p.id NOT IN (
            SELECT
                p.id
            FROM
                ums_admin_permission_relation pr
                    LEFT JOIN ums_permission p ON pr.permission_id = p.id
            WHERE
                pr.type = - 1
              AND pr.admin_id = #{adminId}
        )
        UNION
        SELECT
            p.*
        FROM
            ums_admin_permission_relation pr
                LEFT JOIN ums_permission p ON pr.permission_id = p.id
        WHERE
            pr.type = 1
          AND pr.admin_id = #{adminId}
    </select>
</mapper>

控制层:

package com.lm.controller;

import com.lm.common.api.CommonResult;
import com.lm.mapper.UmsAdminRoleRelationMapper;
import com.lm.pojo.UmsPermission;
import com.lm.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author luoming
 * @date 2022/6/12 23:07
 */
@RestController
public class LoginController {
    @Autowired
    private AdminService adminService;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private UmsAdminRoleRelationMapper umsAdminRoleRelationMapper;


    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public CommonResult login(@RequestParam String username, @RequestParam String password) {
        Map<String, Object> loginResult = adminService.login(username, password);
        if (loginResult.get("token") == null) {
            return CommonResult.validateFailed("用户名或密码错误");
        }
        loginResult.put("tokenHead", tokenHead);
        return CommonResult.success(loginResult);
    }
    @RequestMapping(value = "/permissions",method = RequestMethod.GET)
    public CommonResult permissions(@RequestParam Long  id){
        List<UmsPermission> permissionList = umsAdminRoleRelationMapper.getPermissionList(id);
        List<UmsPermission> failPermission = new ArrayList<>();
        for (UmsPermission umsPermission : permissionList) {
           if (umsPermission.getPid() == 0){
               failPermission.add(setChildren(umsPermission, permissionList));
           }
        }

        return CommonResult.success(failPermission);
    }

    public UmsPermission setChildren(UmsPermission father,List<UmsPermission> permissions){
        List<UmsPermission> list = new ArrayList<>();
        for (UmsPermission permission : permissions) {
            if (father.getId().equals(permission.getPid())){
                father.setChildren(list);
                father.getChildren().add(setChildren(permission,permissions));
            }
        }
        return father;
    }


}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值