springboot整合jwt+spring security 实现接口动态权限控制


基于springboot + security + jwt,实现接口动态权限控制。关于security jwt

数据库结构准备

需要基础表:_user、_role、_user_role、_permission、_role_permission,其中,用户表结构可以不用这么多,但是角色、权限表那些就需要保持一样,才能够跟配置类对应上。

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50650
 Source Host           : localhost:3306
 Source Schema         : hlsd100

 Target Server Type    : MySQL
 Target Server Version : 50650
 File Encoding         : 65001

 Date: 19/11/2021 15:07:47
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for _permission
-- ----------------------------
DROP TABLE IF EXISTS `_permission`;
CREATE TABLE `_permission`  (
  `permission_id` int(11) NOT NULL AUTO_INCREMENT,
  `permission_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `pid` int(11) NOT NULL DEFAULT -1 COMMENT '默认-1,-1代表匿名url,其他代表需要权限。',
  PRIMARY KEY (`permission_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _permission
-- ----------------------------
INSERT INTO `_permission` VALUES (1, '获取用户数组', '/user/**', -1);
INSERT INTO `_permission` VALUES (2, '匿名可访问url', '/swagger-ui.html', -1);
INSERT INTO `_permission` VALUES (3, '匿名可访问url', '/swagger-resources/*', -1);
INSERT INTO `_permission` VALUES (4, '匿名可访问url', '/webjars/*', -1);
INSERT INTO `_permission` VALUES (5, '匿名可访问url', '/*/api-docs', -1);
INSERT INTO `_permission` VALUES (6, '匿名可访问url', '/static/**', -1);
INSERT INTO `_permission` VALUES (7, '匿名可访问url', '/api/auth/**', -1);
INSERT INTO `_permission` VALUES (8, '匿名可访问url', '/error/**', -1);
INSERT INTO `_permission` VALUES (9, '匿名可访问url', '/webSocket/**', -1);
INSERT INTO `_permission` VALUES (10, '匿名可访问url', '/favicon.ico', -1);
INSERT INTO `_permission` VALUES (11, '匿名可访问url', '/auth/**', -1);

-- ----------------------------
-- Table structure for _role
-- ----------------------------
DROP TABLE IF EXISTS `_role`;
CREATE TABLE `_role`  (
  `role_id` int(11) NOT NULL AUTO_INCREMENT,
  `role_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _role
-- ----------------------------
INSERT INTO `_role` VALUES (1, 'admin');
INSERT INTO `_role` VALUES (2, 'root');
INSERT INTO `_role` VALUES (3, 'user');

-- ----------------------------
-- Table structure for _role_permission
-- ----------------------------
DROP TABLE IF EXISTS `_role_permission`;
CREATE TABLE `_role_permission`  (
  `role_permission_id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NULL DEFAULT NULL,
  `permission_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`role_permission_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _role_permission
-- ----------------------------
INSERT INTO `_role_permission` VALUES (1, 1, 1);
INSERT INTO `_role_permission` VALUES (2, 2, 1);

-- ----------------------------
-- Table structure for _user
-- ----------------------------
DROP TABLE IF EXISTS `_user`;
CREATE TABLE `_user`  (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `user_gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `user_tel` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `user_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `user_logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `u_acco` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名',
  `u_pass` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '密码',
  `user_upper_a` int(11) NOT NULL DEFAULT 0 COMMENT '上级a,这里指的是代理商。-1代表没绑定,否则表示是代理商的用户id',
  `user_balance` int(11) NOT NULL DEFAULT 0 COMMENT '余额,如果提现了需要减去,这个字段普通用户跟代理商都会有,分销的时候是加到这个字段,',
  `user_total_balance` int(11) NOT NULL DEFAULT 0 COMMENT ' 总余额,不管有没有提现,都不会减少。-代理商收入跟分销收入都会加到这里',
  `user_upper_b` int(11) NOT NULL DEFAULT 0 COMMENT '上级b,这里指的是代理商。-1代表没绑定,否则表示是代理商的用户id',
  `createtime` bigint(13) NULL DEFAULT NULL,
  `user_sale_amount` int(11) NOT NULL DEFAULT 0 COMMENT ' 销售额度,代理商才会有,普通用户可以不用考虑该字段,代理商收入会添加到这个字段',
  `user_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT ' 二维码',
  `user_visit` int(11) NOT NULL DEFAULT 0,
  `user_version_total` int(11) NOT NULL DEFAULT 0 COMMENT ' ',
  `is_agent` int(11) NOT NULL DEFAULT 0,
  `last_login_time` bigint(13) NULL DEFAULT NULL,
  `user_wx_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `invite_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 517 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _user
-- ----------------------------
INSERT INTO `_user` VALUES (1, 'admin', NULL, NULL, NULL, NULL, 'admin', 'admin', 0, 0, 0, 0, NULL, 0, NULL, 19, 10, 1, NULL, NULL, NULL);
INSERT INTO `_user` VALUES (516, 'ean', '男', '4008823823', '984960659@qq.com', NULL, 'ean', '123456', 0, 0, 0, 0, NULL, 0, NULL, 0, 0, 0, NULL, NULL, NULL);

-- ----------------------------
-- Table structure for _user_role
-- ----------------------------
DROP TABLE IF EXISTS `_user_role`;
CREATE TABLE `_user_role`  (
  `user_role_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  `user_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `user_avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `role_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `status` int(1) NOT NULL DEFAULT 1,
  PRIMARY KEY (`user_role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

jwt相关的工具类

JwtTokenUtils

@Slf4j
@Component
public class JwtTokenUtils implements InitializingBean {

    private final JwtSecurityProperties jwtSecurityProperties;
    private static final String AUTHORITIES_KEY = "auth";
    private Key key;

    @Value(("${jwt.base64-secret}"))
    private String base64Secret;

    public JwtTokenUtils(JwtSecurityProperties jwtSecurityProperties) {
        this.jwtSecurityProperties = jwtSecurityProperties;
    }


    @Override
    public void afterPropertiesSet() {
        byte[] keyBytes = Decoders.BASE64.decode(base64Secret);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    /*登陆的时候调用这个函数生成token,并记录用户信息以及角色,这个claims参数必须要有roles*/
    public String createToken(Map<String, Object> claims) {
        String roles = String.valueOf(claims.get("roles"));
        String ip = String.valueOf(claims.get("ip"));
        if (null == roles || "".equals(roles)) {
            roles = "user";
        }
        Date date = new Date();
        return Jwts.builder()
                .claim(AUTHORITIES_KEY, claims)
                .claim("ROLE", roles)
                .claim("IP", ip)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(date)
                .setExpiration(new Date(date.getTime() + jwtSecurityProperties.getTokenValidityInSeconds()))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(key, SignatureAlgorithm.HS512)
                .compact();
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * @Author Ean
     * @Description 解析token,返回token的角色数组
     * @Date 2021/11/18
     */
    public Authentication getAuthentication(String token, HttpServletRequest httpServletRequest) {
        if ("Yz78r4DSn3yZrSgT".equals(token)) {//处理携带特殊token的情况
            return new UsernamePasswordAuthenticationToken("", token, null);
        }
        Claims claims = Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
        String ip = String.valueOf(claims.get("IP"));
        try {
            String remoteIp = IpUtils.getIpAddr(httpServletRequest);
            if (!remoteIp.equals(ip)) {
                log.error("本次请求IP跟token所携带的IP不一致");
                throw new Exception("本次请求IP跟token所携带的IP不一致");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
//        log.info("ROLE->" + claims.get("ROLE"));
        Collection<? extends GrantedAuthority> authorities =
                Arrays.stream(claims.get("ROLE").toString().split(","))
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

        HashMap map = (HashMap) claims.get("auth");

        User principal = new User(map.get("username").toString(), map.get("password").toString(), authorities);

        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    /*校验token格式是否正确*/
    public boolean validateToken(String authToken, HttpServletRequest httpServletRequest) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(key)
                    .parseClaimsJws(authToken)
                    .getBody();
            String ip = String.valueOf(claims.get("IP"));
            try {
                String remoteIp = IpUtils.getIpAddr(httpServletRequest);
                if (!remoteIp.equals(ip)) {
                    log.error("本次请求IP跟token所携带的IP不一致");
                    throw new Exception("本次请求IP跟token所携带的IP不一致");
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.error("Invalid JWT signature.");
//            e.printStackTrace();
        } catch (ExpiredJwtException e) {
            log.error("Expired JWT token.");
//            e.printStackTrace();
        } catch (UnsupportedJwtException e) {
            log.error("Unsupported JWT token.");
//            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            log.error("JWT token compact of handler are invalid.");
//            e.printStackTrace();
        }
        return false;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(key)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

IpUtils ip获取工具类

public class IpUtils {
    public static String getIpAddr(HttpServletRequest request) throws Exception {
        if (request == null) {
            throw (new Exception("getIpAddr method HttpServletRequest Object is null"));
        }
        String ipString = request.getHeader("x-forwarded-for");
        if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
            ipString = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
            ipString = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
            ipString = request.getRemoteAddr();
        }

        // 多个路由时,取第一个非unknown的ip
        final String[] arr = ipString.split(",");
        for (final String str : arr) {
            if (!"unknown".equalsIgnoreCase(str)) {
                ipString = str;
                break;
            }
        }
        return ipString;
    }
}

yml配置文件

#jwt
jwt:
  header: Authorization
  # 令牌前缀
  token-start-with: Bearer
  # 使用Base64对该令牌进行编码
  base64-secret: 44af5348f21c5ea9d9f80ccc5bddfbf3beebc794b9a4a6258573a841170475a03a03bbd44851882faa0eb2a929ecb0c2e1df12498dafb2764aac83d9d3f5d82d
  # 令牌过期时间 此处单位/毫秒-现在是20分钟
  token-validity-in-seconds: 1200000

spring security相关java类

JwtAccessDeniedHandler

/**
 * @Author Ean
 * @Description 没有授权则通过此类返回信息
 * @Date 2021/11/18
 */
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
    }
}

JwtAuthenticationEntryPoint

/**
 * @Author Ean
 * @Description 没有权限则通过此类返回信息
 * @Date 2021/11/18
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
    }
}

UrlAccessDecisionManager

/**
 * @author Ean
 * @version 0.1.0
 * @Description: 该类的作用是判断用户是否有权限访问当前的url
 * @return
 * @date 2020/8/27 18:06
 * @since 0.1.0
 */
@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {

    /**
     * @param authentication: 当前登录用户的角色信息
     * @param object:         请求url信息
     * @param collection:     `UrlFilterInvocationSecurityMetadataSource`中的getAttributes方法传来的,表示当前请求需要的角色(可能有多个)
     * @return: void
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException {
        if (null != authentication && authentication.getCredentials().equals("Yz78r4DSn3yZrSgT")) {//跳过有这个token的请求
            return;
        }
        // 遍历角色
        for (ConfigAttribute ca : collection) {
            // ① 当前url请求需要的权限
            String needRole = ca.getAttribute();
            if (Constants.ROLE_LOGIN.equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登录!");
                } else {
                    throw new AccessDeniedException("未授权该url!");
                }
            }

            // ② 当前用户所具有的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                // 只要包含其中一个角色即可访问
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("无权访问该接口");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

UrlAccessDeniedHandler

@Component
public class UrlAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setContentType("application/json");
        response.getOutputStream().print(JSONObject.toJSONString(ResultUtil.error(403, "User does not have permission")));
    }
}

UrlFilterInvocationSecurityMetadataSource

/**
 * @author Ean
 * @version 0.1.0
 * @Description: 这个类也比较重要。重写的是security的接口权限判断方法。
 * getAttributes函数会去数据库查询某个url所需要的权限
 * @return
 * @date 2020/8/27 18:07
 * @since 0.1.0
 */
@Component
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    IPermissionService iPermissionService;
    @Autowired
    IRolePermissionService iRolePermissionService;
    @Autowired
    IRoleService iRoleService;
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    /***
     * 返回该url所需要的用户权限信息
     *
     * @param object: 储存请求url信息
     * @return: null:标识不需要任何权限都可以访问
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 获取当前请求url
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        /*放行匿名URL*/
        if (includeUrl(requestUrl)) {
            return null;
        }
        // 数据库中所有url
        Permission[] list = iPermissionService.list(new QueryWrapper<Permission>().ne("pid", -1)).toArray(new Permission[0]);
        for (Permission permission : list) {
            if (null != permission && antPathMatcher.match(permission.getUrl(), requestUrl)) {
                RolePermission[] rolePermissions = iRolePermissionService.list(new QueryWrapper<RolePermission>().eq("permission_id", permission.getPermissionId())).toArray(new RolePermission[0]);
                String[] roles = new String[rolePermissions.length];
                int i = 0;
                if (rolePermissions.length != 0) {
                    Role role;
                    for (RolePermission rolePermission : rolePermissions) {
                        role = iRoleService.getById(rolePermission.getRoleId());
                        roles[i++] = role.getRoleTitle();
                    }
                }
                if (roles.length == 0) {
                    roles = new String[]{Constants.ROLE_LOGIN};
                }
                // 保存该url对应角色权限信息
                return SecurityConfig.createList(roles);
            }
        }

        // 如果数据中没有找到相应url资源则为非法访问,要求用户登录再进行操作
        String[] roleArr = {"user"};//默认角色都是user
        return SecurityConfig.createList(roleArr);
    }

    private boolean includeUrl(String requestUrl) {
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        boolean flag;
        for (Permission permission : WebSecurityConfig.getANNO_URL_LIST()) {
            flag = antPathMatcher.match(permission.getUrl(), requestUrl);
            if (flag) {
                return true;
            }
        }
        return false;
    }

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

    @Override
    public boolean supports(Class<?> aClass) {
        return FilterInvocation.class.isAssignableFrom(aClass);
    }
}

过滤器配置

WebSecurityConfig


/**
 * @Author Ean
 * @Description 总过滤器
 * @Date 2021/11/18
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtTokenUtils jwtTokenUtils;
    public static List<Permission> ANNO_URL_LIST = new ArrayList();

    /*获取匿名可访问数组*/
    public static List<Permission> getANNO_URL_LIST() {
        if (ANNO_URL_LIST.size() <= 0) {
            IPermissionService iPermissionService = (IPermissionService) SpringContextUtil.getBean(IPermissionService.class);
            ANNO_URL_LIST = iPermissionService.list(new QueryWrapper<Permission>().eq("pid", -1));
        }
        return ANNO_URL_LIST;
    }

    /*更新匿名可访问数组*/
    public static void flushANNO_URL_LIST() {
        IPermissionService iPermissionService = (IPermissionService) SpringContextUtil.getBean(IPermissionService.class);
        ANNO_URL_LIST = iPermissionService.list(new QueryWrapper<Permission>().eq("pid", -1));
    }

    /**
     * 获取访问url所需要的角色信息
     */
    private final UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;
    /**
     * 认证权限处理 - 将上面所获得角色权限与当前登录用户的角色做对比,如果包含其中一个角色即可正常访问
     */
    private final UrlAccessDecisionManager urlAccessDecisionManager;
    /**
     * 自定义访问无权限接口时403响应内容
     */
    private final UrlAccessDeniedHandler urlAccessDeniedHandler;

    public WebSecurityConfig(JwtAccessDeniedHandler jwtAccessDeniedHandler, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtTokenUtils jwtTokenUtils, UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource, UrlAccessDecisionManager urlAccessDecisionManager, UrlAccessDeniedHandler urlAccessDeniedHandler) {

        this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtTokenUtils = jwtTokenUtils;
        this.urlFilterInvocationSecurityMetadataSource = urlFilterInvocationSecurityMetadataSource;
        this.urlAccessDecisionManager = urlAccessDecisionManager;
        this.urlAccessDeniedHandler = urlAccessDeniedHandler;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.cors().and()
                // 禁用 CSRF
                .csrf().disable()
                // 授权异常
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler)
                // 防止iframe 造成跨域
                .and()
                .headers()
                .frameOptions()
                .disable()
                // 不创建会话
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 所有请求都需要认证
                .anyRequest().authenticated().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
                o.setAccessDecisionManager(urlAccessDecisionManager);
                return o;
            }
        });
        ;

        // 登录过后访问无权限的接口时自定义403响应内容
        httpSecurity.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler);
        ;
        // 登录过后访问无权限的接口时自定义403响应内容
        httpSecurity.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler);
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        // 添加JWT filter
        httpSecurity.apply(new TokenConfigurer(jwtTokenUtils));

    }

    public static class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

        private final JwtTokenUtils jwtTokenUtils;

        public TokenConfigurer(JwtTokenUtils jwtTokenUtils) {
            this.jwtTokenUtils = jwtTokenUtils;
        }

        @Override
        public void configure(HttpSecurity http) {
            JwtAuthenticationTokenFilter customFilter = new JwtAuthenticationTokenFilter(jwtTokenUtils);
            http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

}

CorsConfigure

/**
 * @Author Ean
 * @Description 跨域配置
 * @Date 2021/11/18
 */
@Configuration
public class CorsConfigure {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        MySecurityProperties mySecurityProperties = new MySecurityProperties();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(mySecurityProperties.corsAllowedHeaders);
        corsConfiguration.setAllowedMethods(mySecurityProperties.corsAllowedMethods);
        corsConfiguration.setAllowedOrigins(mySecurityProperties.corsAllowedOrigins);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(mySecurityProperties.corsPath, corsConfiguration);
        return source;
    }

    private class MySecurityProperties {

        /*允许跨域的资源路径*/
        private String corsPath;

        /*允许跨域的请求头*/
        private List<String> corsAllowedHeaders;

        /*允许跨域的方法*/
        private List<String> corsAllowedMethods;

        /*允许跨的站点*/
        private List<String> corsAllowedOrigins;

        private final List<String> CORS_ALLOWED_HEADERS = Arrays.asList("Authorization", "Content-type");
        private final List<String> CORS_ALLOWED_METHODS = Arrays.asList("GET", "POST", "PUT", "DELETE");

        public MySecurityProperties() {
            this.corsPath = "/**";
            this.corsAllowedHeaders = CORS_ALLOWED_HEADERS;
            this.corsAllowedMethods = CORS_ALLOWED_METHODS;
            this.corsAllowedOrigins = Arrays.asList("*");
        }
    }
}

JwtAuthenticationTokenFilter

/**
 * @author Ean
 * @version 0.1.0
 * @Description: 重点过滤器,该过滤器拦截请求,并校验token。通过则提取角色信息交给security处理
 * @return
 * @date 2020/8/27 18:05
 * @since 0.1.0
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private JwtTokenUtils jwtTokenUtils;

    public JwtAuthenticationTokenFilter(JwtTokenUtils jwtTokenUtils) {
        this.jwtTokenUtils = jwtTokenUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        JwtSecurityProperties jwtSecurityProperties = (JwtSecurityProperties) SpringContextUtil.getBean(JwtSecurityProperties.class);
        String requestRri = httpServletRequest.getRequestURI();
        //获取request token
        String token = null;
        String bearerToken = httpServletRequest.getHeader(jwtSecurityProperties.getHeader());
        if (null!=bearerToken&&bearerToken.equals("Yz78r4DSn3yZrSgT")) {//忽略带有此token的请求
            Authentication authentication = jwtTokenUtils.getAuthentication(bearerToken, httpServletRequest);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtSecurityProperties.getTokenStartWith())) {
            token = bearerToken.substring(jwtSecurityProperties.getTokenStartWith().length());
        }

        /*访问接口的时候,判断token是否有效并从token里边提取用户信息跟角色记录到此次请求中*/
        if (StringUtils.hasText(token) && jwtTokenUtils.validateToken(token,httpServletRequest)) {
            Authentication authentication = jwtTokenUtils.getAuthentication(token,httpServletRequest);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
        } else {
            log.debug("no valid JWT token found, uri: {}", requestRri);
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);

    }
}

授权控制器

AuthController

@Slf4j
@RestController
@RequestMapping("/api/auth")
@Api(tags = "系统授权接口")
public class AuthController {

    private final JwtTokenUtils jwtTokenUtils;
    final
    IPermissionService iPermissionService;
    final
    IUserService userService;

    public AuthController(JwtTokenUtils jwtTokenUtils, IPermissionService iPermissionService, IUserService userService) {
        this.jwtTokenUtils = jwtTokenUtils;
        this.iPermissionService = iPermissionService;
        this.userService = userService;
    }

    @ApiOperation("登录授权")
    @PostMapping(value = "/login")
    public Result login(String uAcco, String uPass, HttpServletRequest httpServletRequest) {
        Map resMap = userService.checkAndGetToken(uAcco, uPass,httpServletRequest);
        if (null != resMap) {
            return ResultUtil.success(resMap);
        }
        return ResultUtil.error(-1, "用户名密码错误");
    }

    @ApiOperation("刷新匿名url")
    @GetMapping("flushANNO_URL")
    public String flushANNO_URL() {
        IPermissionService iPermissionService = (IPermissionService) SpringContextUtil.getBean(IPermissionService.class);
        List<Permission> ANNO_URL_LIST = iPermissionService.list(new QueryWrapper<Permission>().eq("pid", -1));
        WebSecurityConfig.ANNO_URL_LIST = ANNO_URL_LIST;
        return "flush ok";
    }
}

checkAndGetToken

@Override
    public Map checkAndGetToken(String uAcco, String uPass, HttpServletRequest servletRequest) {
        String ip="";
        try {
            ip = IpUtils.getIpAddr(servletRequest);
        } catch (Exception e) {
            e.printStackTrace();
        }
        User user = this.getOne(new QueryWrapper<User>().eq("u_acco", uAcco).eq("u_pass", uPass));
        if (null != user) {
            Map param = new HashMap();
            param.put("username", uAcco);
            param.put("password", uPass);
            param.put("ip", ip);
            String[] roleTitle = roleMapper.getRoleTitle(user.getUserId());
            if (roleTitle.length > 0) {
                param.put("roles", StringUtils.join(roleTitle, ","));
            }
            Map res = new HashMap();
            res.put("Authorization", jwtTokenUtils.createToken(param));
            res.put("user", user);
            return res;
        }
        return null;
    }

pom文件

<!--Security框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.6</version>
        </dependency>


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM