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、数据采集 数据采集的任务就是把数据从各种数据源中采集和存储到数据存储上,…...

「TCG 规范解读」初识 TPM 2.0 库续一
可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Alli…...

task与function
task和function主要是有助于代码的可重用性,都可以在module-endmodule之外声明。 1.function 1.1.function逻辑的综合 function:一个只有1个wire型输出值、全是组合逻辑的函数,且函数名即输出信号名,小括号中按顺序例化输入信号。…...

Android 基础知识4-3.1 TextView(文本框)详解
一、前言 TextView就是一个显示文本标签的控件,就是用来显示文本。可以在代码或者 XML中设置字体,字体大小,字体颜色 ,字体样式 (加粗级斜体),文字截断(比如:只显示10个字…...

点击化学 PEG 试剂1858242-47-3,Propargyl丙炔基-PEG1-乙酸活性酯
Propargyl-PEG1-Acetic acid-NHS ester,丙炔基-聚乙二醇-乙酸琥珀酰亚胺酯,丙炔基-PEG1-乙酸活性酯,丙炔基-PEG1-乙酸-NHS 酯产品规格:1.CAS号:1858242-47-32.分子式:C9H9NO53.分子量:211.174.包…...

正则表达式是如何运作的?
在日常的开发工作当中,我们必不可免的会碰到需要使用正则的情况。 正则在很多时候通过不同的组合方式最后都可以达到既定的目标结果。比如我们有一个需要匹配的字符串: hello,我们可以通过 / .</p>/ 以及 / .?</p>/ 来匹配&…...

JVM参数GC线程数ParallelGCThreads设置
1. ParallelGCThreads参数含义JVM垃圾回收(GC)算法的两个优化标的:吞吐量和停顿时长。JVM会使用特定的GC收集线程,当GC开始的时候,GC线程会和业务线程抢占CPU时间,吞吐量定义为CPU用于业务线程的时间与CPU总消耗时间的比值。为了承…...

java 线程的那些事
什么是进程: 你把它理解成一个软件 什么是线程: 你把它理解成软件里面的一个功能,做的事情 什么是多线程: 你把它理解成 软件里面的某一个功能,原先是一个人累死累活的在那里完成,现在好了,多…...

如何利用 Python 进行客户分群分析(附源码)
每个电子商务数据分析师必须掌握的一项数据聚类技能 如果你是一名在电子商务公司工作的数据分析师,从客户数据中挖掘潜在价值,来提高客户留存率很可能就是你的工作任务之一。 然而,客户数据是巨大的,每个客户的行为都不一样。20…...

D1s RDC2022纪念版开发板开箱评测及点屏教程
作者new_bee 本文转自:https://bbs.aw-ol.com/topic/3005/ 目录 芯片介绍开发板介绍RT-Smart用户态系统编译使用感想引用 1. 芯片介绍 RISC-V架构由于其精简和开源的特性,得到业界的认可,近几年可谓相当热门。操作系统方面有RT-Thread&am…...

了解一下TCP/IP协议族
在《简单说说OSI网络七层模型》中讲到,目前实际使用的网络模型是 TCP/IP 模型,它对 OSI 模型进行了简化,只包含了四层,从上到下分别是应用层、传输层、网络层和链路层(网络接口层),每一层都包含…...