Spring Security+jwt+redis+自定义认证逻辑 权限控制
Spring Security+jwt+redis+自定义认证逻辑 权限控制
1.拦截访问基本思路

2.创建数据库表:角色表(应该6个表,这里只用用户表代替角色表)、权限表、路径表、角色-权限表、权限-路径表
/*
SQLyog Professional v12.14 (64 bit)
MySQL - 5.7.40 : Database - assist_silkworm_db
*********************************************************************
*//*!40101 SET NAMES utf8 */;/*!40101 SET SQL_MODE=''*/;/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`assist_silkworm_db` /*!40100 DEFAULT CHARACTER SET latin1 */;USE `assist_silkworm_db`;/*Table structure for table `t_path` */
# 路径表
DROP TABLE IF EXISTS `t_path`;CREATE TABLE `t_path` (`path_id` bigint(19) NOT NULL COMMENT '路径表id',`path` varchar(200) NOT NULL COMMENT '路径名称',`gmt_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`path_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;/*Data for the table `t_path` */insert into `t_path`(`path_id`,`path`,`gmt_created`,`gmt_modified`) values (1607234202607067138,'/**','2023-02-23 12:07:42','2023-02-23 12:12:47');/*Table structure for table `t_permission` */DROP TABLE IF EXISTS `t_permission`;
# 权限表
CREATE TABLE `t_permission` (`permission_id` bigint(19) NOT NULL COMMENT '主键ID',`permission` varchar(255) NOT NULL COMMENT '权限',`gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`permission_id`),UNIQUE KEY `role` (`permission`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Data for the table `t_permission` */insert into `t_permission`(`permission_id`,`permission`,`gmt_created`,`gmt_modified`) values (1607234202607067138,'ROLE_BIG_ADMIN','2023-02-23 12:10:07','2023-02-23 12:10:07');/*Table structure for table `t_permission_path` */
# 权限路径表
DROP TABLE IF EXISTS `t_permission_path`;CREATE TABLE `t_permission_path` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`permission_id` bigint(19) NOT NULL COMMENT '权限Id',`path_id` bigint(19) NOT NULL COMMENT '权限对应的路径',`gmt_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;/*Data for the table `t_permission_path` */insert into `t_permission_path`(`id`,`permission_id`,`path_id`,`gmt_created`,`gmt_modified`) values (1,1607234202607067138,1607234202607067138,'2023-02-23 12:13:59','2023-02-23 12:13:59');/*Table structure for table `t_user` */
# 用户表
DROP TABLE IF EXISTS `t_user`;CREATE TABLE `t_user` (`user_id` bigint(19) NOT NULL AUTO_INCREMENT COMMENT '主键',`username` varchar(100) NOT NULL COMMENT '账号',`password` varchar(100) NOT NULL COMMENT '密码',`gmt_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`is_del` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1607234202607067139 DEFAULT CHARSET=utf8;/*Data for the table `t_user` */insert into `t_user`(`user_id`,`username`,`password`,`gmt_created`,`gmt_modified`,`is_del`) values (1607234202607067138,'admin','$2a$10$GE0hWDRIksPXpZCDtTEFP.8EKi25OQ8PPvc6Q14YzSyzpkkQzDPxW','2022-12-26 12:37:50','2023-02-23 12:09:05',0);/*Table structure for table `t_user_permission` */DROP TABLE IF EXISTS `t_user_permission`;
# 用户权限表
CREATE TABLE `t_user_permission` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`user_id` bigint(19) NOT NULL COMMENT '用户Id',`permission_id` bigint(19) NOT NULL COMMENT '权限Id',`gmt_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;/*Data for the table `t_user_permission` */insert into `t_user_permission`(`id`,`user_id`,`permission_id`,`gmt_created`,`gmt_modified`) values (2,1607234202607067138,1607234202607067138,'2023-02-23 12:13:06','2023-02-23 12:13:06');/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
3.导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.7.4</version>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2aKwzdu-1677162858470)(SpringSecurity%20+%20JWT-%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6.assets/image-20230223222442753.png)]
###4.创建实现UserDestails的实现类
package com.rfos.assistsilkworm.config.security;import com.rfos.assistsilkworm.pojo.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;/*** @author hjt* @date 2022/10/4 20:55* @description*/@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailsDTO implements UserDetails {private User user;private List<String> permissionList;private Collection<? extends GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();for (String permission : permissionList) {//拥有的权限名SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);grantedAuthorityList.add(simpleGrantedAuthority);}setAuthorities(grantedAuthorityList);return grantedAuthorityList;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
UserDetails类:
UserDetails(位于org.springframework.security.core.userdetails包下)主要和用户信息有关的接口,该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象Authentication中去
public interface UserDetails extends Serializable {// 权限// 用户的权限集, 默认需要添加ROLE_ 前缀Collection<? extends GrantedAuthority> getAuthorities();// 密码// 用户的加密后的密码, 不加密会使用{noop}前缀String getPassword();// 用户名String getUsername();// 帐户未过期boolean isAccountNonExpired();// 帐户未锁定boolean isAccountNonLocked();// 凭证是否过期boolean isCredentialsNonExpired();// 用户是否可用boolean isEnabled();
}
角色表等各个表对应的实体类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ToXl9gvR-1677162858471)(SpringSecurity%20+%20JWT-%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6.assets/image-20230223222708838.png)]
package com.rfos.assistsilkworm.pojo;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;/*** <p>* * </p>** @author rfos* @since 2023-02-23*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_user")
@ApiModel(value="User对象", description="")
public class User implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "主键")@TableId(value = "user_id", type = IdType.ASSIGN_ID)private Long userId;@ApiModelProperty(value = "账号")private String username;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty("创建时间")//创建时间由SQL完成@TableField(fill = FieldFill.INSERT)private Date gmtCreated;@ApiModelProperty("更新时间")//更新时间由数据库完成@TableField(fill = FieldFill.INSERT_UPDATE)private Date gmtModified;@ApiModelProperty(value = "是否删除")private Boolean isDel;}
package com.rfos.assistsilkworm.pojo;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;
import java.io.Serializable;import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import springfox.documentation.annotations.ApiIgnore;/*** <p>* * </p>** @author rfos* @since 2023-02-23*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_path")
@ApiModel(value="Path对象", description="")
public class Path implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "路径表id")@TableId(value = "path_id", type = IdType.ASSIGN_ID)private Long pathId;@ApiModelProperty(value = "路径名称")private String path;@ApiModelProperty("创建时间")//创建时间由SQL完成@TableField(fill = FieldFill.INSERT)private Date gmtCreated;@ApiModelProperty("更新时间")//更新时间由数据库完成@TableField(fill = FieldFill.INSERT_UPDATE)private Date gmtModified;}
package com.rfos.assistsilkworm.pojo;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;/*** <p>* * </p>** @author rfos* @since 2023-02-23*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_permission")
@ApiModel(value="Permission对象", description="")
public class Permission implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "主键ID")@TableId(value = "permission_id", type = IdType.ASSIGN_ID)private Long permissionId;@ApiModelProperty(value = "权限")private String permission;@ApiModelProperty("创建时间")//创建时间由SQL完成@TableField(fill = FieldFill.INSERT)private Date gmtCreated;@ApiModelProperty("更新时间")//更新时间由数据库完成@TableField(fill = FieldFill.INSERT_UPDATE)private Date gmtModified;}
package com.rfos.assistsilkworm.pojo;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;/*** <p>* * </p>** @author rfos* @since 2023-02-23*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_permission_path")
@ApiModel(value="PermissionPath对象", description="")
public class PermissionPath implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "主键ID")@TableId(value = "id", type = IdType.AUTO)private Integer id;@ApiModelProperty(value = "权限Id")private Long permissionId;@ApiModelProperty(value = "权限对应的路径")private Long pathId;@ApiModelProperty("创建时间")//创建时间由SQL完成@TableField(fill = FieldFill.INSERT)private Date gmtCreated;@ApiModelProperty("更新时间")//更新时间由数据库完成@TableField(fill = FieldFill.INSERT_UPDATE)private Date gmtModified;}
package com.rfos.assistsilkworm.pojo;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;/*** <p>* * </p>** @author rfos* @since 2023-02-23*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_user_permission")
@ApiModel(value="UserPermission对象", description="")
public class UserPermission implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "主键ID")@TableId(value = "id", type = IdType.AUTO)private Integer id;@ApiModelProperty(value = "用户Id")private Long userId;@ApiModelProperty(value = "权限Id")private Long permissionId;@ApiModelProperty("创建时间")//创建时间由SQL完成@TableField(fill = FieldFill.INSERT)private Date gmtCreated;@ApiModelProperty("更新时间")//更新时间由数据库完成@TableField(fill = FieldFill.INSERT_UPDATE)private Date gmtModified;}
5.创建自定义认证逻辑-UserDetailsService的实现类-UserDetailServiceImpl类
UserDetailsService接口
package org.springframework.security.core.userdetails;
public interface UserDetailsService {UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
UserDetailServiceImpl类
package com.rfos.assistsilkworm.config.security;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rfos.assistsilkworm.pojo.Permission;
import com.rfos.assistsilkworm.pojo.User;
import com.rfos.assistsilkworm.pojo.UserPermission;
import com.rfos.assistsilkworm.service.PermissionService;
import com.rfos.assistsilkworm.service.UserPermissionService;
import com.rfos.assistsilkworm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** @author hjt* @date 2022/10/5 11:24* @description*/
@Service("userDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {//用户业务@Autowiredprivate UserService userService;@Autowiredprivate PermissionService permissionService;@Autowiredprivate UserPermissionService userPermissionService;/*重写方法:username: 需要其数据的用户的用户名UsernameNotFoundException: 找不到用户的异常*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//获取用户并判断是否存在User user = userService.getByName(username);if(ObjectUtils.isEmpty(user)){return null;}//封装用户信息至对应的认证类中UserDetailsDTO userDetailsDTO = new UserDetailsDTO();userDetailsDTO.setUser(user);//TODO 获取角色对应的权限List<UserPermission> userPermissions = userPermissionService.list(new QueryWrapper<UserPermission>().eq("user_id", user.getUserId()));//TODO 获取权限id对应的所有权限List<String> pathList = new ArrayList<>();for (UserPermission userPermission : userPermissions) {List<Permission> permissionList = permissionService.list(new QueryWrapper<Permission>().eq("permission_id", userPermission.getPermissionId()));pathList.addAll(permissionList.stream().map(data-> data.getPermission()).collect(Collectors.toList()));}userDetailsDTO.setPermissionList(pathList);return userDetailsDTO;}}
6.JwtUtil工具类说明
/*** JWT工具类*/
public class JwtUtil {//有效期为public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000 一个小时//设置秘钥明文public static final String JWT_KEY = "sangeng";public static String getUUID() {//随机生成token字符串String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成jtw** @param subject token中要存放的数据(json格式)* @return*/public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间return builder.compact();}/*** 生成jtw** @param subject token中要存放的数据(json格式)* @param ttlMillis token超时时间* @return*/public static String createJWT(String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间return builder.compact();}private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {//签名的算法SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//生成获取密钥SecretKey secretKey = generalKey();long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if (ttlMillis == null) {ttlMillis = JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid) //唯一的ID.setSubject(subject) // 主题 可以是JSON数据.setIssuer("sg") // 签发者.setIssuedAt(now) // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);//设置过期时间}/*** 创建token** @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间return builder.compact();}/*** 生成加密后的秘钥 secretKey** @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}
}
7.,创建过滤器JwtAuthenticationTokenFilter类拦截指定路径验证访问信息
OncePerRequestFilter:继承OncePerRequestFilter用于继承实现并在每次请求时只执行一次过滤
package com.rfos.assistsilkworm.config.security;import com.alibaba.fastjson.JSONObject;
import com.rfos.assistsilkworm.config.redis.RedisUtils;
import com.rfos.assistsilkworm.config.security.UserDetailsDTO;
import com.rfos.assistsilkworm.constant.CommonConstants;
import com.rfos.assistsilkworm.enums.ResultCode;
import com.rfos.assistsilkworm.exception.ApiException;
import com.rfos.assistsilkworm.util.IPAddrUtil;
import com.rfos.assistsilkworm.util.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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;
import java.util.Objects;/*** @date 2022/10/4*/
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisUtils redisUtil;//保证一次请求只调用一次doFilterInternal方法@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {log.info("用户访问requestURI={},ip={}", request.getRequestURI(), IPAddrUtil.getIpAddress(request));//获取请求头中的tokenString token = request.getHeader(CommonConstants.TOKEN_HEAD);//如果没有token则为第一次访问,过滤器放行-将请求转发给过滤器链上下一个对象if (!StringUtils.hasText(token)) {filterChain.doFilter(request, response);return;}//解析tokenlog.info("获取到请求的token:\n"+token);String userId;try {Claims claims = JwtUtils.parseJWT(token);userId = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new ApiException(ResultCode.FAIL_AUTHORITY);}//从redis中获取用户信息Object obj = redisUtil.get(CommonConstants.TOKEN_HEAD + ":" + userId);if (Objects.isNull(obj)) {throw new ApiException(ResultCode.EMPTY_USER);}//获取认证对象的信息UserDetailsDTO userDetailsDTO = JSONObject.parseObject(obj.toString(), UserDetailsDTO.class);log.info("用户访问requestURI={},username={},ip={}", request.getRequestURI(), userDetailsDTO.getUsername(), IPAddrUtil.getIpAddress(request));//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中log.info("获取权限对应的所有权限:\n{}",userDetailsDTO.getAuthorities());/*UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication所以当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication),然后生成的Authentication会被交由AuthenticationManager来进行管理而AuthenticationManager管理一系列的AuthenticationProvider,而每一个Provider都会通UserDetailsService和UserDetail来返回一个以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication*/UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userDetailsDTO, null, userDetailsDTO.getAuthorities());//将用户信息保存在安全上下文中SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}}
8.创建自定义的UrlFilterInvocationSecurityMetadataSource类实现FilterInvocationSecurityMetadataSource接口
FilterInvocationSecurityMetadataSource:拦截url判断用户url的访问权限是否包含该用户的权限
package com.rfos.assistsilkworm.config.security;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rfos.assistsilkworm.pojo.Path;
import com.rfos.assistsilkworm.pojo.PermissionPath;
import com.rfos.assistsilkworm.service.PathService;
import com.rfos.assistsilkworm.service.PermissionPathService;
import com.rfos.assistsilkworm.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.security.access.SecurityConfig;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;/*** @author hjt* @Package com.kdcloud.srd.security* @date 2022/10/5 20:14* @description 获取url匹配的用户权限**/
@Component
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {//没有权限private static final String ROLE_NONE = "ROLE_NONE";@Autowiredprivate PermissionPathService permissionPathService;@Autowiredprivate PermissionService permissionService;@Autowiredprivate PathService pathService;//路径匹配类AntPathMatcher antPathMatcher = new AntPathMatcher();//开放的路径private final static String[] OPEN_PATH_LIST = new String[]{"/**","/static/**","/templates/**","/index","/login","/admin/login","/home/**","/user/login","/swagger-ui.html","/swagger-resources/**","/*/api-docs","/webjars/**","/v2/**","/api/**","/actuator/**"};//filterInvocation:获取该请求@Overridepublic Collection<ConfigAttribute> getAttributes(Object requestObj) throws IllegalArgumentException {FilterInvocation filterInvocation = (FilterInvocation) requestObj;String requestUrl = filterInvocation.getRequestUrl();System.out.println("请求路径url:"+requestUrl);//开放部分路径//静态资源不拦截if(isMatcherAllowedRequest(filterInvocation)){return null;}// 数据库中所有urlList<Path> pathList = pathService.list();pathList.forEach(value-> System.out.println(value.getPath()));for (Path path : pathList) {// 获取该url所对应的权限boolean match = antPathMatcher.match(path.getPath(), requestUrl);System.out.println(match);if (match) {System.out.println("匹配的路径url\n:"+path);//获取路径对应的所有权限List<PermissionPath> permissionPaths = permissionPathService.list(new QueryWrapper<PermissionPath>().eq("path_id", path.getPathId()));List<String> permissionPathList = new ArrayList<>();if (!CollectionUtils.isEmpty(permissionPaths)){for (PermissionPath permissionPath : permissionPaths) {String roleName = permissionService.getById(permissionPath.getPermissionId()).getPermission();permissionPathList.add(roleName);}}// 能访问url对应角色权限信息System.out.println("能访问url对应角色权限信息:"+permissionPathList);return SecurityConfig.createList(permissionPathList.stream().toArray(String[]::new));}}// 如果数据中没有找到相应url资源则为非法访问return SecurityConfig.createList(ROLE_NONE);}/*** 判断当前请求是否在允许请求的范围内* @param fi 当前请求* @return 是否在范围中*/private boolean isMatcherAllowedRequest(FilterInvocation fi){return allowedRequest().stream().map(AntPathRequestMatcher::new).filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest())).toArray().length > 0;}/*** @return 定义允许请求的列表*/private List<String> allowedRequest(){return Arrays.asList(OPEN_PATH_LIST);}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}/*** 表示返回的对象是否支持校验* @param aClass* @return*/@Overridepublic boolean supports(Class<?> aClass) {return FunctionalInterface.class.isAssignableFrom(aClass);}}
9.决策访问处理器:创建自定义的JwtAccessDeniedManager实现AccessDeniedManager接口
package com.rfos.assistsilkworm.config.security;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.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;import java.util.Collection;/*** @author hjt* @date 2022/10/5 23:29* @description决策访问处理器:创建自定义的JwtAccessDeniedManager实现AccessDeniedManager接口*/
@Component
public class JwtAccessDeniedManager implements AccessDecisionManager {/*** auth:认证信息* collection:角色信息*/@Overridepublic void decide(Authentication auth, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {Collection<? extends GrantedAuthority> auths = auth.getAuthorities();for (ConfigAttribute configAttribute : collection) {if ("ROLE_LOGIN".equals(configAttribute.getAttribute())&& auth instanceof UsernamePasswordAuthenticationToken) {return;}for (GrantedAuthority authority : auths) {if (configAttribute.getAttribute().equals(authority.getAuthority())) {return;}}}throw new AccessDeniedException("权限不足");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}
10.创建Spring Security的配置类securityConfig
package com.rfos.assistsilkworm.config.security;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启注解匹配
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {//自动装配自定义逻辑@Autowired@Qualifier("userDetailsServiceImpl")private UserDetailsService userDetailsService;//过滤器JwtAuthenticationTokenFilter@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;//拒绝访问处理@Autowiredprivate AccessDeniedHandler accessDeniedHandler;//决策处理器@Autowiredprivate JwtAccessDeniedManager jwtAccessDeniedManager;//请求访问url处理过滤器@Autowiredprivate UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;//密码加密@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}//加密密码public static void main(String[] args) {SpringSecurityConfig springSecurityConfig = new SpringSecurityConfig();PasswordEncoder passwordEncoder = springSecurityConfig.passwordEncoder();String encode = passwordEncoder.encode("admin");System.out.println("加密后的密码:"+encode);}//配置访问路径@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//把token校验过滤器添加到过滤器链中http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//请求需要经过权限认证http.authorizeRequests().anyRequest().authenticated()//自定义的元数据源和权限决策配置.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O obj) {obj.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);obj.setAccessDecisionManager(jwtAccessDeniedManager);return obj;}});//配置异常处理器 => 自定义的http.exceptionHandling()//配置认证失败处理器.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);//允许跨域http.cors();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//使用自己的UserDetailsServiceauth.userDetailsService(userDetailsService);}}
11.创建登录接口,生成封装验证信息
package com.rfos.assistsilkworm.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.rfos.assistsilkworm.common.CommonResult;
import com.rfos.assistsilkworm.dto.LoginDTO;
import com.rfos.assistsilkworm.pojo.User;/*** <p>* 服务类* </p>** @author rfos* @since 2022-11-02*/
public interface UserService extends IService<User> {/*** 管理员登录验证* @param loginDTO* @return*/CommonResult login(LoginDTO loginDTO);User getByName(String username);
}
package com.rfos.assistsilkworm.service.impl;import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.util.StringUtil;
import com.rfos.assistsilkworm.common.CommonResult;
import com.rfos.assistsilkworm.config.redis.RedisUtils;
import com.rfos.assistsilkworm.config.security.UserDetailsDTO;
import com.rfos.assistsilkworm.constant.CommonConstants;
import com.rfos.assistsilkworm.constant.RedisConstants;
import com.rfos.assistsilkworm.dto.LoginDTO;
import com.rfos.assistsilkworm.enums.ResultCode;
import com.rfos.assistsilkworm.exception.ApiException;
import com.rfos.assistsilkworm.mapper.UserMapper;
import com.rfos.assistsilkworm.pojo.User;
import com.rfos.assistsilkworm.service.UserService;
import com.rfos.assistsilkworm.util.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Objects;/*** <p>* 服务实现类* </p>** @author rfos* @since 2022-11-02*/
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisUtils redisUtils;@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic CommonResult login(LoginDTO loginDTO) {//封装用户认证信息UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);if (Objects.isNull(authenticate)) {throw new ApiException("用户名或密码错误");}//使用username生成token令牌UserDetailsDTO userDetailsDTO = (UserDetailsDTO) authenticate.getPrincipal();String userId = String.valueOf(userDetailsDTO.getUser().getUserId());String jwt = JwtUtils.createJWT(userId);log.info("使用username生成token:\n"+jwt);//将认证信息authenticate存入redislog.info("authenticate存入redis:\n{}",userDetailsDTO);redisUtils.set(CommonConstants.TOKEN_HEAD +":"+ userId, JSONObject.toJSONString(userDetailsDTO));return CommonResult.success(jwt);}@Overridepublic User getByName(String username) {QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();if(StringUtil.isNotEmpty(username)){userQueryWrapper.eq("username",username);userQueryWrapper.eq("is_del", CommonConstants.IS_NOT_DEL);}User user = userMapper.selectOne(userQueryWrapper);return user;}}相关文章:
Spring Security+jwt+redis+自定义认证逻辑 权限控制
Spring Securityjwtredis自定义认证逻辑 权限控制 1.拦截访问基本思路 2.创建数据库表:角色表(应该6个表,这里只用用户表代替角色表)、权限表、路径表、角色-权限表、权限-路径表 /* SQLyog Professional v12.14 (64 bit) MySQL…...
打游戏什么蓝牙耳机好用?打游戏比较好的蓝牙耳机
游戏耳机提供身临其境的细致声音,同时也是与朋友在线聊天的绝佳通信设备,尤其对于游戏玩家来说,聆听和被聆听的最佳方式之一就是游戏耳机,那2023年到底有哪些值得购买的游戏耳机呢?现在就让我们一起来看看吧。 第一款…...
炔基点击交联试剂1704097-05-1,Alkyne-A-DSBSO crosslinker,发生相应点击反应
1、理论分析:中文名:炔基-A-DSBSO crosslinker,英文名:Alkyne-A-DSBSO crosslinkerCAS号:1704097-05-1化学式:C25H32N2O12S2分子量:616.652、产品详情:外观:白色固体&…...
刷题记录:牛客NC24309Overplanting (Silver)
传送门:牛客 题目描述: Farmer John has purchased a new machine that is capable of planting grass within any rectangular region of his farm that is "axially aligned" (i.e., with vertical and horizontal sides). Unfortunately, the machine malfunc…...
Spring Boot中使用Sa-Token实现轻量级登录与鉴权
1. Sa-Token 介绍 Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。 功能结构图 2. 登录认证 对于一些登录之后才能访问的接口(例如&…...
《分布式技术原理与算法解析》学习笔记Day20
CAP理论 什么是CAP理论? CAP理论用来指导分布式系统设计,以保证系统的可用性、数据一致性等。 C,Consistency,一致性,指所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后ÿ…...
【2023-2-23】FastDeploy 安装教程
【2023-2-22】FastDeploy 安装编译教程 该测试 FastDeploy CPU版本。 1. fastDeploy库编译 1.1 官方预编译库下载 预编译库下载安装 1.2 自定义CPU版本库编译 官方编译FastDeploy教程 CMakeGUI VS 2019 IDE编译FastDeploy 本人编译教程 CMAKE_CONFIGURATION_TYPES 属性设…...
rollup.js 一个简单实用的打包工具
最近在看vue3相关的知识的时候,发现了一个新的打包工具,至少于我而言是新鲜的。它就是rollup.js。一说到JS打包、合并、压缩、模块处理等都会想到webpack,这是王者,当然入门的难度偏高。而vue3中搭配的vite运行速度确实非常快&…...
数据结构与算法之最小爬楼梯费用动态规划
继续上一道题目,在上一道题目的基础之上,我们来解决这一道爬楼梯最小费用题。一.题目描述二.思路(动态规划五部曲)确定dp数组以及下标的含义使用动态规划,就要有一个数组来记录状态,本题只需要一个一维数组dp[i]就可以了。dp[i]的…...
阿里云ACA认证如何获取?
获取阿里云ACA(Alibaba Cloud Certification Associate)认证,需要按照以下步骤进行操作: 注册阿里云账号。如果您还没有阿里云账号,请先注册一个账号。登录阿里云官网。登录后,进入阿里云认证中心。选择AC…...
【Python入门第十六天】Python If ... Else
Python 条件和 If 语句 Python 支持来自数学的常用逻辑条件: 等于:a b不等于:a ! b小于:a < b小于等于:a < b大于:a > b大于等于:a > b 这些条件能够以多种方式使用,…...
两数之和的解法
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案…...
领导催我优化SQL语句,我求助了ChatGPT。这是ChatGPT给出的建议,你们觉得靠谱吗
作为一个程序员,无论在面试还是工作中,优化SQL都是绕不过去的难题。 为啥?工作之后才会明白,随着公司的业务量增多,SQL的执行效率对程系统运行效率的影响逐渐增大,相对于改造代码,优化SQL语句是…...
ArcGIS手动分割矢量面要素从而划分为多个面部分的方式:Cut Polygons Tool
本文介绍在ArcGIS下属ArcMap软件中,通过“Cut Polygons Tool”工具,对一个面要素矢量图层加以手动分割,从而将其划分为指定形状的多个部分的方法。 对于一个面要素矢量文件,有时我们需要对其加以划分,通过手动勾勒新的…...
【LeetCode】剑指 Offer 13. 机器人的运动范围 p92 -- Java Version
题目链接:https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/ 1. 题目介绍(13. 机器人的运动范围) 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动࿰…...
[oeasy]python0091_仙童公司_八叛逆_intel_8080_altair8800_牛郎星
编码进化 个人电脑 计算机 通过电话网络 进行连接 极客 利用技术 做一些有趣的尝试 极客文化 是 认真研究技术的 文化 计算机 不再是 高校和研究机构高墙里面的 神秘事物而是 生活中常见的 家用电器 ibm 蓝色巨人脚步沉重 dec 小型机不断蚕食低端市场甚至组成网络干掉大型机…...
crontab 执行脚本报错,手动执行脚本正常的解决方法
一、出现的问题 有一个守护脚本XXX.sh,需要使用oracle用户在linux上配置定时任务,每1分钟检查执行一次。但是发现该脚本使用oralce用户手动启动没问题,能正常把程序启动起来,而使用crontab并没有把程序启动起来。 二、排查分析问…...
扎心话题 | 设计院背后的潜规则你知道吗?
大家好,我是建模助手。 大家都知道,在过去的2022年经济是真难!以小编所在的广东为例,全年GDP增长仅1.9%。 这个数据足以呈现一个社会现象——不仅消费力咔咔下降,各行各业更有不同程度地嗝屁。这其中也包括一些设计院…...
【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…...
大数据核心技术是什么
大数据的核心层:数据采集层、数据存储与分析层、数据共享层、数据应用层,可能叫法有所不同本质上的角色都大同小异。 大数据的核心技术都包括什么? 1、数据采集 数据采集的任务就是把数据从各种数据源中采集和存储到数据存储上,…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
