赞
踩
SpringSecurity是spring的一个安全管理框架,相比另一个安全框架shiro,它提供了更丰富的功能,社区资源也比shiro丰富。
web应用主要用于认证和授权
认证:验证当前访问系统的是不是本系统用户,并且要确定具体是哪个用户
授权:经过认证完成过后判断当前用户是否具有某个权限操作具体资源
springsecurity的核心就是认证和授权,并且更快的整合soring应用
首先搭建一个soringBoot工程
pom依赖
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.5.0</version>
- </parent>
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- </dependencies>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
创建一个测试小例子
- @RestController
- public class HelloController {
- @GetMapping("/hello")
- public String hello() {
- return "hello security";
- }
- }
访问http://localhost:8080/hello, 返回字符串hello security
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
再次登录会需要输入用户名和密码,此时默认用户名为: user 密码为控制台输入的密码
Using generated security password: e493f2ae-cb1e-4802-b0a2-1e8da5d97068
根据入门案例,分析具体流程
springsecurity底层实现是通过一系列的过滤器链完成登录验证和授权等功能,主要是使用到下面15个过滤器
调用流程
认证流程介绍
接口和类介绍:
UsernamePasswordAuthenticationFilter: 认证过滤器主要用于生成Authentication对象完成用户信息封装。
AuthenticationManager: 定义了认证Authentication的方法。
AbstractUserDetailsAuthenticationProvider:提供了authenticate()认证方法,并且在里面调用UserDetailsServiceloadUserByUsername查询用户信息。
UserDetailsService:获取用户信息的核心接口,里面定义了根据用户名查询用户信息,可以实现该接口覆盖默认的用户信息查询方法,完成自定义用户信息查询,返回一个UserDetails用户信息封装对象。
UserDetails: 主要封装用户相关信息,通过UserDetailsService获取用户信息返回UserDetails对象,然后将相关信息设置到Authentication对象中。
登录
自定义登录接口
调用ProviderManager的认证方法,如果认证通过生成jwt并存入到redis
自定义UserDetailsService
通过自定义的实现完成从数据库查询用户信息和权限信息并返回UserDetails对象
校验
自定义jwt认证过滤器
获取生成jwt token,解析token获取userid,通过userid从redis中获取用户信息,存入到SecurityContextHolder中,方便后续其他过滤器通过SecurityContextHolder获取用户信息
什么是 JSON Web Token?
JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
尽管 JWT 可以加密以提供各方之间的保密性,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名的一方。
跨平台 跨语言 轻量级json 对象进行数据传输, 数字签名保证安全性
应用场景:
授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用。
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确定发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改
JSON Web Token令牌结构
JSON Web 令牌由三部分组成,中间用 . 分割
页眉
有效荷载
签名
JWT通常如: xxxxx.yyyyy.zzzzz 组成
结构详解
页眉 headers 需要Base64进行编码
页面主要包含令牌类型(令牌类型目前只有一种 JWT)和签名算法例如HMAC SHA256 RSA
例如:
- {
- "alg" : "SHA256",
- "typ" : "JWT"
- }
有效荷载 payload 需要Base64进行编码
荷载主要包含声明,声明分为已注册声明、公共声明、专用声明
已注册声明: 由JWT提前帮我们预定义好的例如iss(发行人),exp(到期时间),sub(主题),aud(受众)等。
公共声明: 这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在 IANA JSON Web 令牌注册表中定义,或者定义为包含抗冲突命名空间的 URI。
专用声明: 这个由用户自定义可以是一个json字符串用户可以将实体信息转换后放入专用声明中,
注意:这些声明中不要包含任何敏感信息
例如
- {
- "sub": "1234567890",
- "name": "John Doe",
- "admin": true
- }
签名
要创建签名部分,首先需要Base64编码页眉和有效荷载json数据,然后通过页面中指定的算法以及密钥进行算法加密获取签名的结果。
密钥:是用户自定义的一系列字符串作为私钥,不能提供给其他系统
签名示例
// 头部信息 { "alg": "HS256", "typ": "JWT" } // 有效荷载 { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } // 通过指定算法签名 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret ) // 签名后的结果 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- public class JwtTest {
- // 生成jwt
- @Test
- public void contextLoads() {
- HashMap headers = new HashMap();
- Calendar instance = Calendar.getInstance();
- instance.add(Calendar.SECOND, 200);
- String token = JWT.create().withHeader(headers) //headers
- .withClaim("userid", "21")
- .withClaim("username", "wangm") //payload
- .withExpiresAt(instance.getTime())
- .sign(Algorithm.HMAC256("1231312sdasddsas"));// 密钥
- System.out.println(token);
-
- }
- // 验证jwt 可以获取页眉数据和有效荷载数据
- @Test
- public void checkToken() {
- JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("1231312sdasddsas")).build();
- DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzI0OTMxMTAsInVzZXJpZCI6IjIxIiwidXNlcm5hbWUiOiJ3YW5nbSJ9.DNmrAFgRnWdcAtBK6nkwNPV-GSsrKyO_TyoIeB0YqzI");
- String userid = decodedJWT.getClaim("userid").asString();
- System.out.println(userid);
-
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
jwt封装工具类
- @Component
- public class JwtUtils {
- private static String secretKey;
- @Value("${JWT.secretKey}")
- public void secretKey(String secretKey) {
- JwtUtils.secretKey = secretKey;
- }
- /*
- * @description: 生成token
- * @author: wangm
- * @date: 2022/12/31 21:47
- * @param: [map, expDay]
- * @return: java.lang.String
- **/
- public static String generateToken(Map<String, String> map, Integer expDay) {
- Calendar instance = Calendar.getInstance();
- instance.add(Calendar.SECOND, 1);
- JWTCreator.Builder builder = JWT.create();
- map.forEach((k, v) ->{
- builder.withClaim(k, v);
- });
- builder.withExpiresAt(instance.getTime());
- // 生成token
- return builder.sign(Algorithm.HMAC256(secretKey));
- }
- /*
- * @description: 验证token
- * @author: wangm
- * @date: 2022/12/31 21:47
- * @param: [token]
- * @return: void
- **/
- public static void verifyToken(String token) {
- // 验证token
- JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
-
- }
- /*
- * @description: 获取token组成信息
- * @author: wangm
- * @date: 2022/12/31 21:48
- * @param: [token]
- * @return: com.auth0.jwt.interfaces.DecodedJWT
- **/
- public static DecodedJWT getTokenInfo(String token) {
- DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
- return decodedJWT;
- }
- }
- com.jack.study.security.entity.LoginUser [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[admin]]
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
jwt整合springboot 整合拦截器统一令牌验证
- // 整合拦截器实现统一token 验证
- public class JwtInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- String token = request.getHeader("token");
- Map<String, Object> map = new HashMap<>();
- if (StrUtil.isEmpty(token)) {
- map.put("status", "514");
- map.put("message", "token为空");
- String s = new ObjectMapper().writeValueAsString(map);
- response.setContentType("application/json;charset=UTF-8");
- response.setCharacterEncoding("UTF-8");
- response.getWriter().write(s);
- return false;
- }
- // jwt异常处理
- try{
- JwtUtils.verifyToken(token);
- return true;
- }catch (SignatureVerificationException e1) {
- map.put("status", "510");
- map.put("message", "签名异常");
- }catch (TokenExpiredException e2) {
- map.put("status", "511");
- map.put("message", "token已过期");
- }catch (AlgorithmMismatchException e3) {
- map.put("status", "512");
- map.put("message", "加密算法异常");
- }catch (JWTDecodeException e4) {
- map.put("status", "513");
- map.put("message", "token解密异常");
- }catch (Exception e) {
- map.put("status", "500");
- map.put("message", "系统异常");
- }
- String s = new ObjectMapper().writeValueAsString(map);
- response.setContentType("application/json;charset=UTF-8");
- response.setCharacterEncoding("UTF-8");
- response.getWriter().write(s);
- return false;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
创建一个maven 工程 添加相关依赖
pom.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.jack.study</groupId>
- <artifactId>spring-security-token</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.5.0</version>
- </parent>
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- </dependency>
- <!--引入spring security 依赖-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <!--redis 依赖-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!-- jwt 依赖 -->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
- <!-- fastjson -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.83</version>
- </dependency>
- <!--swagger 整合bootstarp ui-->
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>swagger-bootstrap-ui</artifactId>
- <version>1.9.6</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>2.9.2</version>
- <exclusions>
- <exclusion>
- <groupId>io.swagger</groupId>
- <artifactId>swagger-models</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>io.swagger</groupId>
- <artifactId>swagger-models</artifactId>
- <version>1.5.21</version>
- </dependency>
-
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.6.2</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.3</version>
- </dependency>
- </dependencies>
-
- </project>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
创建启动类 并配置相关扫描
- @SpringBootApplication(scanBasePackages = {"com.jack.study"}) // 组件扫描路径
- @MapperScan(basePackages = {"com.jack.study.*.mapper"}) // mapper文件扫描
- public class SecurityTokenApplication {
- public static void main(String[] args) {
- SpringApplication.run(SecurityTokenApplication.class, args);
- }
- }
定义yml配置
server: port: 8080 spring: #数据库连接配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/security-jwt?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC username: root password: 123456 #redis 连接配置 redis: host: localhost port: 6379 password: 123456 mybatis-plus: #mapper xml扫描配置 mapper-locations: classpath:/mapper/*.xml #sql执行日志输出配置 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #jwt 自定义配置 Jwt: #jwt签名私钥 secretKey: wwwwww #jwt 令牌有效期 单位毫秒 ttlMillis: 360000
项目相关工具类和统一封装
响应统一封装实体类
- //响应代码枚举
- public enum ResultCode {
- /***
- *
- * @description:
- * # 200 表示请求成功
- * #1000~1999 区间表示参数错误
- * #2000~2999 区间表示用户错误
- * #3000~3999 区间表示接口异常
- * #4000~4999 区间表示网络请求错误
- * #5000~5999 区间表示服务器内部错误
- * @Date: 2020/11/4 13:25
- **/
- //请求成功
- SUCCESS(200,"成功"),
- PARAM_IS_INVALID(1001,"参数为空"),
- OK(200, "操作成功"),
- FAIL(500, "操作失败"),
- NO_AUTH(403, "没有权限"),
- NO_PAGE(404, "未找到页面"),
- TOKEN_OVERDUE(400, "Token超期");
- //参数校检
-
- private Integer code;
- private String message;
- ResultCode(Integer code,String message){
- this.code = code;
- this.message = message;
- }
- public Integer code(){
- return this.code;
- }
- public String message(){
- return this.message;
- }
-
-
- }
-
- // 响应结果实体
- @Data
- public class Result implements Serializable {
- /**
- * 返回码
- */
- private Integer code;
- /**
- * 返回消息
- */
- private String message;
- /**
- * 返回数据
- */
- private Object data;
- public Result(){
-
-
- }
- //自定义错误代码和错误信息
- public Result(Integer code, String message, Object data) {
- this.code = code;
- this.message = message;
- this.data = data;
- }
-
- //根据枚举固定错误代码获取
- public Result(ResultCode resultCode, Object data){
- this.code = resultCode.code();
- this.message = resultCode.message();
- this.data = data;
-
- }
- /***
- *
- * @description: 操作成功不返回数据
- * @Date: 2020/11/4 16:36
- * @return: com.currency.server.entity.response.Result
- **/
- public static Result success(){
- Result result = new Result();
- result.setCode(ResultCode.SUCCESS.code());
- result.setMessage(ResultCode.SUCCESS.message());
- return result;
- }
- /***
- *
- * @description: 操作成功 返回请求数据
- * @Date: 2020/11/4 16:36
- * @return: com.currency.server.entity.response.Result
- **/
- public static Result success(Object data){
- Result result = new Result();
- result.setCode(ResultCode.SUCCESS.code());
- result.setMessage(ResultCode.SUCCESS.message());
- result.setData(data);
- return result;
-
- }
- /***
- *
- * @description: 操作失败 返回请求数据
- * @Date: 2020/11/4 16:36
- * @return: com.currency.server.entity.response.Result
- **/
- public static Result failure(ResultCode resultCode){
- Result result = new Result();
- result.setCode(resultCode.code());
- result.setMessage(resultCode.message());
- return result;
-
- }
- /***
- *
- * @description: 操作失败 返回请求数据
- * @Date: 2020/11/4 16:36
- * @return: com.currency.server.entity.response.Result
- **/
- public static Result failure(Integer code, String message){
- Result result = new Result();
- result.setCode(code);
- result.setMessage(message);
- return result;
-
- }
- /***
- *
- * @description: 操作成功 返回请求数据
- * @Date: 2020/11/4 16:36
- * @return: com.currency.server.entity.response.Result
- **/
- public static Result failure(ResultCode resultCode,Object data){
- Result result = new Result();
- result.setCode(resultCode.code());
- result.setMessage(resultCode.message());
- result.setData(data);
- return result;
-
- }
- /***
- *
- * @description: 自定义返回码和返回信息
- * @Date: 2020/11/4 16:36
- * @return: com.currency.server.entity.response.Result
- **/
- public static Result custom(Integer code, String message, Object data){
- Result result = new Result();
- result.setCode(code);
- result.setMessage(message);
- result.setData(data);
- return result;
-
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
异常封装统一处理
认证异常工具类
- public class AuthExceptionUtil {
- public static Result getErrMsgByExceptionType(AuthenticationException e) {
- if (e instanceof LockedException) {
- return Result.failure(1100, "账户被锁定,请联系管理员!");
- } else if (e instanceof CredentialsExpiredException) {
- return Result.failure(1105,"用户名或者密码输入错误!");
- }else if (e instanceof InsufficientAuthenticationException) {
- return Result.failure(403,"权限不足请登录!");
- } else if (e instanceof AccountExpiredException) {
- return Result.failure(1101, "账户过期,请联系管理员!");
- } else if (e instanceof DisabledException) {
- return Result.failure(1102, ("账户被禁用,请联系管理员!"));
- } else if (e instanceof BadCredentialsException) {
- return Result.failure(1105, "用户名或者密码输入错误!");
- }else if (e instanceof AuthenticationServiceException) {
- return Result.failure(1106, "认证失败,请重试!");
- }
-
- return Result.failure(1200, e.getMessage());
- }
- public static Result getErrMsgByExceptionType(AccessDeniedException e) {
- if (e instanceof CsrfException) {
- return Result.failure(-1001, "非法访问跨域请求异常!");
- } else if (e instanceof org.springframework.security.web.csrf.CsrfException) {
- return Result.failure(-1002,"非法访问跨域请求异常!");
- } else if (e instanceof AuthorizationServiceException) {
- return Result.failure(1101, "认证服务异常请重试!");
- }else if (e instanceof AccessDeniedException) {
- return Result.failure(4003, "权限不足不允许访问!");
- }
-
- return Result.failure(1200, e.getMessage());
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
认证业务异常封装
- public class BusinessException extends AuthenticationException {
- public BusinessException(String msg, Throwable cause) {
- super(msg, cause);
- }
-
- public BusinessException(String msg) {
- super(msg);
- }
- }
统一异常处理,根据自定义异常分别处理错误信息
- @RestControllerAdvice
- public class GlobalExceptionHandler {
-
- // 根据实际自定义的异常统一处理
- @ExceptionHandler(value = BusinessException.class)
- public Result operationError(BusinessException e) {
- return Result.failure(ResultCode.FAIL, e.getMessage());
- }
-
- }
redisConfig 配置类
- @Configuration
- public class RedisConfig {
-
-
- public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
- RedisTemplate<Object, Object> template = new RedisTemplate<>();
- template.setConnectionFactory(connectionFactory);
-
- FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
-
- // 使用StringRedisSerializer来序列化和反序列化redis的key值
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(serializer);
-
- // Hash的key也采用StringRedisSerializer的序列化方式
- template.setHashKeySerializer(new StringRedisSerializer());
- template.setHashValueSerializer(serializer);
-
- template.afterPropertiesSet();
- return template;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
fastjoson 序列化
- public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
- public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
- private Class<T> clazz;
- static
- {
- ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
- }
-
- public FastJsonRedisSerializer(Class<T> clazz)
- {
- super();
- this.clazz = clazz;
- }
-
- public byte[] serialize(T t) throws SerializationException
- {
- if (t == null)
- {
- return new byte[0];
- }
- return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
- }
-
-
- public T deserialize(byte[] bytes) throws SerializationException
- {
- if (bytes == null || bytes.length <= 0)
- {
- return null;
- }
- String str = new String(bytes, DEFAULT_CHARSET);
-
- return JSON.parseObject(str, clazz);
- }
- protected JavaType getJavaType(Class<?> clazz)
- {
- return TypeFactory.defaultInstance().constructType(clazz);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
redis 工具类封装
- @Component
- @Order(-1)
- public final class RedisUtil {
-
- @Autowired
- private RedisTemplate redisTemplate;
-
- /**
- * 指定缓存失效时间
- *
- * @param key 键
- * @param time 时间(秒)
- * @return 0
- */
-
- public boolean expire(String key, long time) {
- try {
- if (time > 0) {
- redisTemplate.expire(key, time, TimeUnit.SECONDS);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 根据key 获取过期时间
- *
- * @param key 键 不能为null
- * @return 时间(秒) 返回0代表为永久有效
- */
- public long getExpire(String key) {
- return redisTemplate.getExpire(key, TimeUnit.SECONDS);
- }
-
- /**
- * 判断key是否存在
- *
- * @param key 键
- * @return true 存在 false不存在
- */
- public boolean hasKey(String key) {
- try {
- return redisTemplate.hasKey(key);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 删除缓存
- *
- * @param key 可以传一个值 或多个
- */
- @SuppressWarnings("unchecked")
- public void del(String... key) {
-
- if (key != null && key.length > 0) {
- if (key.length == 1) {
- redisTemplate.delete(key[0]);
- } else {
- redisTemplate.delete(CollectionUtils.arrayToList(key));
- }
- }
- }
-
- // ============================String=============================
-
- /**
- * 普通缓存获取
- *
- * @param key 键
- * @return 值
- */
- public Object get(String key) {
- return key == null ? null : redisTemplate.opsForValue().get(key);
- }
-
- /**
- * 普通缓存放入
- *
- * @param key 键
- * @param value 值
- * @return true成功 false失败
- */
- public boolean set(String key, Object value) {
- try {
- redisTemplate.opsForValue().set(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 普通缓存放入并设置时间
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
- * @return true成功 false 失败
- */
- public boolean set(String key, Object value, long time) {
- try {
- if (time > 0) {
- redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
- } else {
- set(key, value);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
-
- /**
- * 递增
- *
- * @param key 键
- * @param delta 要增加几(大于0)
- * @return
- */
- public long incr(String key, long delta) {
-
- if (delta < 0) {
- throw new RuntimeException("递增因子必须大于0");
- }
- return redisTemplate.opsForValue().increment(key, delta);
- }
-
-
- /**
- * 递减
- *
- * @param key 键
- * @param delta 要减少几(小于0)
- * @return
- */
- public long decr(String key, long delta) {
-
- if (delta < 0) {
- throw new RuntimeException("递减因子必须大于0");
- }
- return redisTemplate.opsForValue().increment(key, -delta);
- }
-
- // ================================Map=================================
-
- /**
- * HashGet
- *
- * @param key 键 不能为null
- * @param item 项 不能为null
- * @return 值
- */
-
- public Object hget(String key, String item) {
- return redisTemplate.opsForHash().get(key, item);
- }
-
-
- /**
- * 获取hashKey对应的所有键值
- *
- * @param key 键
- * @return 对应的多个键值
- */
-
- public Map<Object, Object> hmget(String key) {
- return redisTemplate.opsForHash().entries(key);
- }
-
- /**
- * HashSet
- *
- * @param key 键
- * @param map 对应多个键值
- * @return true 成功 false 失败
- */
-
- public boolean hmset(String key, Map<String, Object> map) {
- try {
- redisTemplate.opsForHash().putAll(key, map);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * HashSet 并设置时间
- *
- * @param key 键
- * @param map 对应多个键值
- * @param time 时间(秒)
- * @return true成功 false失败
- */
- public boolean hmset(String key, Map<String, Object> map, long time) {
- try {
- redisTemplate.opsForHash().putAll(key, map);
- if (time > 0) {
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 向一张hash表中放入数据,如果不存在将创建
- *
- * @param key 键
- * @param item 项
- * @param value 值
- * @return true 成功 false失败
- */
- public boolean hset(String key, String item, Object value) {
- try {
- redisTemplate.opsForHash().put(key, item, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 向一张hash表中放入数据,如果不存在将创建
- * 0
- *
- * @param key 键
- * @param item 项
- * @param value 值
- * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
- * @return true 成功 false失败
- */
- public boolean hset(String key, String item, Object value, long time) {
- try {
- redisTemplate.opsForHash().put(key, item, value);
- if (time > 0) {
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 删除hash表中的值
- *
- * @param key 键 不能为null
- * @param item 项 可以使多个 不能为null
- */
- public void hdel(String key, Object... item) {
- redisTemplate.opsForHash().delete(key, item);
- }
-
- /**
- * 判断hash表中是否有该项的值
- *
- * @param key 键 不能为null
- * @param item 项 不能为null
- * @return true 存在 false不存在
- */
- public boolean hHasKey(String key, String item) {
- return redisTemplate.opsForHash().hasKey(key, item);
- }
-
- /**
- * hash递增 如果不存在,就会创建一个 并把新增后的值返回
- *
- * @param key 键
- * @param item 项
- * @param by 要增加几(大于0)
- * @return
- */
- public double hincr(String key, String item, double by) {
- return redisTemplate.opsForHash().increment(key, item, by);
- }
-
- /**
- * hash递减
- *
- * @param key 键
- * @param item 项
- * @param by 要减少记(小于0)
- * @return
- */
- public double hdecr(String key, String item, double by) {
- return redisTemplate.opsForHash().increment(key, item, -by);
- }
-
- // ============================set=============================
-
- /**
- * 根据key获取Set中的所有值
- *
- * @param key 键
- * @return
- */
- public Set<Object> sGet(String key) {
- try {
- return redisTemplate.opsForSet().members(key);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * 根据value从一个set中查询,是否存在
- *
- * @param key 键
- * @param value 值
- * @return true 存在 false不存在
- */
- public boolean sHasKey(String key, Object value) {
-
- try {
- return redisTemplate.opsForSet().isMember(key, value);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将数据放入set缓存
- *
- * @param key 键
- * @param values 值 可以是多个
- * @return 成功个数
- */
- public long sSet(String key, Object... values) {
- try {
- return redisTemplate.opsForSet().add(key, values);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 将set数据放入缓存
- *
- * @param key 键
- * @param time 时间(秒)
- * @param values 值 可以是多个
- * @return 成功个数
- */
- public long sSetAndTime(String key, long time, Object... values) {
-
- try {
- Long count = redisTemplate.opsForSet().add(key, values);
- if (time > 0)
- expire(key, time);
- return count;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 获取set缓存的长度
- *
- * @param key 键
- * @return
- */
- public long sGetSetSize(String key) {
- try {
- return redisTemplate.opsForSet().size(key);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
-
- /**
- * 移除值为value的
- *
- * @param key 键
- * @param values 值 可以是多个
- * @return 移除的个数
- */
- public long setRemove(String key, Object... values) {
- try {
- Long count = redisTemplate.opsForSet().remove(key, values);
- return count;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- // ===============================list=================================
-
- /**
- * 获取list缓存的内容
- *
- * @param key 键
- * @param start 开始
- * @param end 结束 0 到 -代表所有值
- * @return
- */
- public List<Object> lGet(String key, long start, long end) {
-
- try {
- return redisTemplate.opsForList().range(key, start, end);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * 获取list缓存的长度
- *
- * @param key 键
- * @return 0
- */
- public long lGetListSize(String key) {
- try {
- return redisTemplate.opsForList().size(key);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 通过索引 获取list中的值
- *
- * @param key 键
- * @param index 索引 index>=0时, 0 表头, 第二个元素,依次类推;index<0时,-,表尾,-倒数第二个元素,依次类推
- * @return 0
- */
- public Object lGetIndex(String key, long index) {
- try {
- return redisTemplate.opsForList().index(key, index);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @return
- */
- public boolean lSet(String key, Object value) {
- try {
- redisTemplate.opsForList().rightPush(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒)
- * @return
- */
- public boolean lSet(String key, Object value, long time) {
- try {
- redisTemplate.opsForList().rightPush(key, value);
- if (time > 0)
- expire(key, time);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @return
- */
- public boolean lSet(String key, List<Object> value) {
- try {
- redisTemplate.opsForList().rightPushAll(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒)
- * @return 0
- */
- public boolean lSet(String key, List<Object> value, long time) {
- try {
- redisTemplate.opsForList().rightPushAll(key, value);
- if (time > 0)
- expire(key, time);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 根据索引修改list中的某条数据
- *
- * @param key 键
- * @param index 索引
- * @param value 值
- * @return 0
- */
- public boolean lUpdateIndex(String key, long index, Object value) {
- try {
- redisTemplate.opsForList().set(key, index, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 移除N个值为value
- *
- * @param key 键
- * @param count 移除多少个
- * @param value 值
- * @return 移除的个数
- */
- public long lRemove(String key, long count, Object value) {
- try {
- Long remove = redisTemplate.opsForList().remove(key, count, value);
- return remove;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
webUtil response 响应封装
- public class WebUtils {
- public static String rednerString(HttpServletResponse response, String content) {
- try{
- response.setStatus(200);
- response.setContentType("application/json;charset=utf-8");
- response.setCharacterEncoding("UTF-8");
- response.getWriter().print(content);
- }catch (Exception e){
- e.printStackTrace();
- }
- return null;
- }
- }
从前面的分析可以知道我们自定义个UserDetailsService 实现类即可完成从数据库通过用户名和密码完成认证
创建用户表
- CREATE TABLE `user` (
- `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
- `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户名',
- `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',
- `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '昵称',
- `status` varchar(50) NOT NULL DEFAULT '0' COMMENT '账号状态(0 正常 1 停用)',
- `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱',
- `phonenumber` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '联系方式',
- `sex` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户性别(0 男 1 女 2未知)',
- `avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '头像',
- `user_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户类型(0 管理员 1 普通用户)',
- `create_by` bigint DEFAULT NULL COMMENT '创建人id',
- `create_time` datetime DEFAULT NULL COMMENT '创建时间',
- `update_by` bigint DEFAULT NULL COMMENT '更新人id',
- `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
- `del_flag` int NOT NULL DEFAULT '0' COMMENT '删除标志(0 未删除 1 已删除)',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
用户实体类
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- @TableName("user")
- public class UserDO implements Serializable {
- private static final long serialVersionUID = -7637085672143873931L;
- /*
- * 主键id
- **/
- private Long id;
- /*
- * 用户名
- **/
- private String username;
- /*
- * 密码
- **/
- private String password;
- /*
- * 昵称
- **/
- private String nickname;
- /*
- * 账号状态(0 正常 1 停用)
- **/
- private String status;
- /*
- * 邮箱
- **/
- private String email;
- /*
- * 联系方式
- **/
- private String phonenumber;
- /*
- * 用户性别(0 男 1 女 2未知)
- **/
- private String sex;
- /*
- * 头像
- **/
- private String avatar;
- /*
- * 用户类型(0 管理员 1 普通用户)
- **/
- private String userType;
- /*
- * 创建人id
- **/
- private Long createBy;
- /*
- * 创建时间
- **/
- private LocalDateTime createTime;
- /*
- * 更新人id
- **/
- private Long updateBy;
- /*
- * 更新时间
- **/
- private LocalDateTime updateTime;
- /*
- * 删除标志(0 未删除 1 已删除)
- **/
- private Integer delFlag;
-
-
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
定义UserMapper和相关方法
- public interface UserMapper extends BaseMapper<UserDO> {
- }
定义UserService和相关方法
- public interface UserService extends IService<UserDO> {
- UserDO getUserByUsername(String username);
- }
定义UserServiceImpl和相关方法
- @Service
- public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {
- @Autowired
- private UserMapper userMapper;
- @Override
- public UserDO getUserByUsername(String username) {
- UserDO userDO = userMapper.selectOne(new QueryWrapper<UserDO>().lambda().eq(UserDO::getUsername, username));
- if (Objects.isNull(userDO)) {
- throw new BusinessException("用户信息不存在!");
- }
- return userDO;
- }
- }
创建角色表
- CREATE TABLE `sys_role` (
- `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
- `name` varchar(128) NOT NULL COMMENT '角色名称',
- `role_key` varchar(100) NOT NULL COMMENT '角色代码',
- `status` char(1) NOT NULL DEFAULT '0' COMMENT '角色状态(0 正常 1 停用)',
- `del_flag` int NOT NULL DEFAULT '0' COMMENT '删除标志(0 正常 -1 已删除)',
- `create_by` bigint DEFAULT NULL COMMENT '创建人',
- `update_by` bigint DEFAULT NULL COMMENT '更新人',
- `create_time` datetime DEFAULT NULL COMMENT '创建时间',
- `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
- `remark` varchar(500) DEFAULT NULL COMMENT '备注',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表';
创建SysRole实体类
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class SysRole {
- /*
- * 主键id
- **/
- private Long id;
- /*
- * 角色名称
- **/
- private String name;
- /*
- * 角色代码
- **/
- private String roleKey;
- /*
- * 角色状态(0 正常 1 停用)
- **/
- private String status;
- /*
- * 删除标志(0 正常 -1 已删除)
- **/
- private Integer delFlag;
- /*
- * 创建人
- **/
- private Long createBy;
- /*
- * 更新人
- **/
- private Long updateBy;
- /*
- * 创建时间
- **/
- private LocalDateTime createTime;
- /*
- * 更新时间
- **/
- private LocalDateTime updateTime;
- /*
- * 备注
- **/
- private String remark;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
创建SysRoleMapper接口
- public interface SysRoleMapper extends BaseMapper<SysRole> {
- List<String> selectRolesByUserId(@Param("userId") Long userId);
- }
创建SysRoleService 接口
- public interface SysRoleService extends IService<SysRole> {
- List<String> selectRolesByUserId(@Param("userId") Long userId);
-
- }
创建SysRoleService 接口实现类SysRoleServiceImpl
- @Service
- public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
- @Autowired
- private SysRoleMapper sysRoleMapper;
-
- @Override
- public List<String> selectRolesByUserId(Long userId) {
- return sysRoleMapper.selectRolesByUserId(userId);
- }
- }
创建SysRoleMapper.xml
- <?xml version="1.0" encoding="utf-8" ?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
- <mapper namespace="com.jack.study.role.mapper.SysRoleMapper">
- <select id="selectRolesByUserId" resultType="java.lang.String">
- select sr.role_key from sys_role sr
- left join sys_user_role sur on sur.role_id = sr.id
- <where>
- sr.status ='0'
- <if test="userId != null">
- and sur.user_id=#{userId}
- </if>
- </where>
- </select>
- </mapper>
创建角色表
- CREATE TABLE `sys_menu` (
- `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
- `menu_name` varchar(64) NOT NULL COMMENT '菜单名称',
- `path` varchar(200) NOT NULL COMMENT '菜单路径',
- `component` varchar(255) NOT NULL COMMENT '组件',
- `visible` char(1) NOT NULL DEFAULT '0' COMMENT '菜单显示(0 显示 1 隐藏)',
- `status` char(1) NOT NULL DEFAULT '0' COMMENT '菜单状态(0 启用 1 停用)',
- `perms` varchar(100) NOT NULL COMMENT '权限集合标识',
- `icon` varchar(100) DEFAULT NULL COMMENT '图标',
- `create_by` bigint DEFAULT NULL COMMENT '创建人',
- `update_by` bigint DEFAULT NULL COMMENT '更新人',
- `create_time` datetime DEFAULT NULL COMMENT '创建时间',
- `update_time` datetime DEFAULT NULL COMMENT '更新时间',
- `del_flag` int NOT NULL DEFAULT '0' COMMENT '删除标志(0 正常 -1 已删除)',
- `remark` varchar(500) DEFAULT NULL COMMENT '备注',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单表';
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
创建SysMenu实体类
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class SysMenu {
- private Long id;
- private String menuName;
- private String path;
- private String component;
- private String visible;
- private String status;
- private String perms;
- private String icon;
- private Long createBy;
- private Long updateBy;
- private LocalDateTime createTime;
- private LocalDateTime updateTime;
- private Integer delFlag;
- private String remark;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
创建SysMenuMapper接口
- public interface SysMenuMapper extends BaseMapper<SysMenu> {
- List<String> selectPermsByUserId(@Param("userId") Long userId);
- }
创建SysMenuService接口
- public interface SysMenuService extends IService<SysMenu> {
- List<String> selectPermsByUserId(Long userId);
- }
创建SysMenuService接口实现类SysMenuServiceImpl
- @Service
- public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
- @Autowired
- private SysMenuMapper sysMenuMapper;
- @Override
- public List<String> selectPermsByUserId(Long userId) {
- return sysMenuMapper.selectPermsByUserId(userId);
- }
- }
创建SysMenuMapper.xml
- <?xml version="1.0" encoding="utf-8" ?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
- <mapper namespace="com.jack.study.menu.mapper.SysMenuMapper">
-
-
- <select id="selectPermsByUserId" resultType="java.lang.String">
- select sm.perms from sys_user_role sur
- left join sys_role sr on sr.id = sur.role_id
- left join sys_role_menu srm on srm.role_id = sr.id
- left join sys_menu sm on sm.id = srm.menu_id
- <where>
- sr.`status`='0'
- and sm.status = 0
- <if test="userId != null ">
- and sur.user_id=#{userId}
- </if>
- </where>
- group by sm.perms
-
-
-
- </select>
-
-
- </mapper>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
用户角色sql
- CREATE TABLE `sys_user_role` (
- `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',
- `role_id` bigint NOT NULL COMMENT '角色id',
- PRIMARY KEY (`user_id`,`role_id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表';
用户角色实体
- @Data
- public class SysUserRole {
- /*
- * 用户id
- **/
- private Long userId;
- /*
- * 角色id
- **/
- private Long roleId;
- }
角色菜单sql
- CREATE TABLE `sys_role_menu` (
- `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'roleId',
- `menu_id` bigint NOT NULL COMMENT 'menuId',
- PRIMARY KEY (`role_id`,`menu_id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='sys_role_menu';
登录登出LoginService
- public interface LoginService {
- Result login(UserDto userDto);
-
- Result logout();
- }
- @Service
- public class LoginServiceImpl implements LoginService {
- @Autowired
- private RedisUtil redisUtil;
- @Autowired
- private AuthenticationManager authenticationManager;
- @Override
- public Result login(UserDto userDto) {
- // 1 获取AuthenticationManager 对象 然后调用 authenticate() 方法
- // UsernamePasswordAuthenticationToken 实现了Authentication 接口
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDto.getUsername(), userDto.getPassword());
- Authentication authenticate = authenticationManager.authenticate(authenticationToken);
- //2 认证没通过 提示认证失败
- if (Objects.isNull(authenticate)) {
- throw new BusinessException("认证失败用户信息不存在");
- }
- //认证通过 使用userid 生成jwt token令牌
- LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
- String s = loginUser.getUserDO().getId().toString();
- String token = JwtUtils.createJwt(s);
- // 把用户信息存入到redis中
- Map<String, Object> map = new HashMap<>();
- map.put("token", token);
- redisUtil.set("login:"+s, loginUser);
- return Result.success(map);
- }
-
- @Override
- public Result logout() {
- // 1 获取 SecurityContextHolder 中的用户id
- UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
- LoginUser loginUser = (LoginUser)authentication.getPrincipal();
- //2 删除redis 中的缓存信
- String key = "login:"+loginUser.getUserDO().getId().toString();
- redisUtil.del(key);
- return Result.success("退出成功!");
-
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
具体配置需要将其他组件写好再配置
- @Service
- public class MyUserDetailsService implements UserDetailsService {
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-
- return null;
- }
- }
具体配置需要将其他组件写好再配置
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- }
再认证过程中通过自定义的UserDetailsService,重写了loadUserByUsername 方法返回UserDetails对象,我们可以通过实现UserDetails 完成自定义用户信息返回
自定义LoginUser对象
- @Data
- @NoArgsConstructor
- public class LoginUser implements UserDetails{
- // 用户信息
- private UserDO userDO;
- // 权限信息
- private List<String> permissions;
- @JSONField(serialize = false)
- private List<SimpleGrantedAuthority> authorities;
- public LoginUser(UserDO userDO, List<String> permissions){
- this.userDO = userDO;
- this.permissions = permissions;
- }
-
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- // 将权限信息封装成 SimpleGrantedAuthority
- if (authorities != null) {
- return authorities;
- }
- authorities = this.permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
- return authorities;
- }
-
- @Override
- public String getPassword() {
- return this.userDO.getPassword();
- }
-
- @Override
- public String getUsername() {
- return this.userDO.getUsername();
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
自定义MyUserDetailsService,实现自定义的用户名密码校验
- @Service
- public class MyUserDetailsService implements UserDetailsService {
- // 用户信息
- @Autowired
- private UserService userService;
- //权限菜单信息
- @Autowired
- private SysMenuService sysMenuService;
- //角色信息
- @Autowired
- private SysRoleService sysRoleService;
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- UserDO userDO = userService.getUserByUsername(username);
- // 获取用户所有的角色
- List<String> roles = sysRoleService.selectRolesByUserId(userDO.getId());
- Set<String> set = roles.stream().map(s -> "ROLE_" + s).collect(Collectors.toSet());
- // 获取用户所有的权限
- List<String> permissions = sysMenuService.selectPermsByUserId(userDO.getId());
- permissions.addAll(set);
- return new LoginUser(userDO, permissions);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
自定义认证失败处理AuthenticationEntryPointImpl
- @Component
- public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
- @Override
- public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
- WebUtils.rednerString(httpServletResponse, JSONUtil.toJsonStr(AuthExceptionUtil.getErrMsgByExceptionType(e)));
- }
- }
自定义授权失败处理AccessDeniedHandlerImpl
- @Component
- public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
- @Override
- public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
- WebUtils.rednerString(httpServletResponse, JSONUtil.toJsonStr(AuthExceptionUtil.getErrMsgByExceptionType(e)));
-
- }
- }
自定义jwt 认证token过滤器JwtAuthenticationFilter
- @Component
- public class JwtAuthenticationFilter extends OncePerRequestFilter {
- @Autowired
- private RedisUtil redisUtil;
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws BusinessException, ServletException, IOException {
- // 获取token
- String token = request.getHeader("token");
- if (StrUtil.isEmpty(token)) {
- // token不存在 放行 并且直接return 返回
- filterChain.doFilter(request, response);
- return;
- }
- // 解析token
- String userId = null;
- try {
- Claims claims = JwtUtils.parseJwt(token);
- userId = claims.getSubject();
- } catch (Exception e) {
- throw new BusinessException("token非法");
- }
- // 获取userid 从redis中获取用户信息
- String redisKey = "login:" + userId;
- LoginUser loginUser = (LoginUser)redisUtil.get(redisKey);
- if (Objects.isNull(loginUser)) {
- throw new BusinessException("用户未登录");
-
- }
-
- //将用户信息存入到SecurityContextHolder
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- // 放行
- filterChain.doFilter(request, response);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
自定义CorsConfig配置
- @Configuration
- public class CorsConfig implements WebMvcConfigurer {
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- registry.addMapping("/**")
- .allowedOriginPatterns("*")
- .allowCredentials(true)
- .allowedMethods("GET", "POST", "PUT", "DELETE")
- .maxAge(3600);
-
- }
- @Bean
- public CorsConfiguration corsConfiguration() {
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.addAllowedOrigin("*");
- corsConfiguration.addAllowedHeader("*");
- corsConfiguration.addAllowedMethod("*");
- corsConfiguration.addExposedHeader("token");
- return corsConfiguration;
- }
- @Bean
- public CorsFilter corsFilter() {
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", corsConfiguration());
- return new CorsFilter(source);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
SpringSecurity核心配置
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private JwtAuthenticationFilter jwtAuthenticationFilter;
-
- @Autowired
- private AccessDeniedHandlerImpl accessDeniedHandler;
-
- @Autowired
- private AuthenticationEntryPointImpl authenticationEntryPoint;
- // 自定义密码加密配置常用的BCryptPasswordEncoder,也可以用其他的
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 关闭csrf
- .csrf().disable()
- // 不通过session 获取SecurityContext
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .authorizeRequests()
- // 允许登录接口匿名访问
- .antMatchers("/user/login", "/user/test").anonymous()
- // 其他请求都需要认证
- .anyRequest().authenticated();
- // 在UsernamePasswordAuthenticationFilter 过滤器之前执行jwtAuthenticationFilter
- http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
- // 认证授权异常自定义处理
- http.exceptionHandling()
- .authenticationEntryPoint(authenticationEntryPoint)
- .accessDeniedHandler(accessDeniedHandler);
- // 跨域请求配置
- http.cors();
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
编写LoginController
- @RestController
- public class LoginController {
- @Autowired
- private LoginService loginService;
- // 登录接口 需要传入用户名和密码
- @PostMapping("/user/login")
- public Result login(@RequestBody UserDto userDto) {
- Result result = loginService.login(userDto);
- return result;
- }
-
- @PostMapping("/user/hello")
- public Result login() {
-
- return Result.success();
- }
- //等处配置
- @GetMapping("/user/logout")
- public Result logout() {
- return loginService.logout();
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
使用postman工具进行测试
登录接口测试
退出接口测试,需要在请求头中携带token
鉴权测试
当前用户权限为编码
- select sm.perms from sys_user_role sur
- left join sys_role sr on sr.id = sur.role_id
- left join sys_role_menu srm on srm.role_id = sr.id
- left join sys_menu sm on sm.id = srm.menu_id
- where
- sr.`status`='0'
- and sm.status = 0
-
- and sur.user_id=1
-
- group by sm.perms
- @RestController
- public class HelloController {
- @GetMapping("/hello/test")
- // 只有system:dept:list:test 权限才能访问
- @PreAuthorize("hasAuthority('system:dept:list:test')")
- public String hello() {
- return "hello";
- }
-
- }
请求头中携带登录token,测试结果
正常访问
- @RestController
- public class HelloController {
- @GetMapping("/hello/test")
- // 只有system:dept:list:test 权限才能访问
- @PreAuthorize("hasAuthority('system:dept:list')")
- public String hello() {
- return "hello";
- }
-
- }
返回 字符串 "hello"
其他鉴权测试同上,下面讲解其他鉴权的含义
- @RestController
- public class HelloController {
- @GetMapping("/hello/test")
- //必须包含system:dept:list 权限才能访问否则提示没有权限
- //@PreAuthorize("hasAuthority('system:dept:list')")
- // 只需要有其中一个权限就能访问
- //@PreAuthorize("hasAnyAuthority('system:dept:list555','system:dept:list')")
- //只需要包含其中一个角色就能访问
- //@PreAuthorize("hasAnyRole('ceo', 'admin')")
- //必须包含指定角色才能访问
- //@PreAuthorize("hasRole('ceo')")
- // 自定义权限判断
- @PreAuthorize("@ex.hasAuthority('ROLE_ceo')")
- public String hello() {
- return "hello";
- }
-
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
源码探究
不管是hasAuthority 、hasAnyAuthority、 hasAnyRole、 hasRole都是在SecurityExpressionRoot类中实现
SecurityExpressionRoot
- public final boolean hasAuthority(String authority) {
- return this.hasAnyAuthority(authority);
- }
-
- public final boolean hasAnyAuthority(String... authorities) {
- return this.hasAnyAuthorityName((String)null, authorities);
- }
-
- public final boolean hasRole(String role) {
- return this.hasAnyRole(role);
- }
-
- public final boolean hasAnyRole(String... roles) {
- return this.hasAnyAuthorityName(this.defaultRolePrefix, roles);
- }
-
- private boolean hasAnyAuthorityName(String prefix, String... roles) {
- //获取当前用户的所有权限和角色
- Set<String> roleSet = this.getAuthoritySet();
- String[] var4 = roles;
- int var5 = roles.length;
-
- for(int var6 = 0; var6 < var5; ++var6) {
- String role = var4[var6];
- String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
- // 如果当前用户里面包含注解里面的角色或者权限则返回true 允许访问
- if (roleSet.contains(defaultedRole)) {
- return true;
- }
- }
- //返回false 不允许访问
- return false;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
this.getAuthoritySet() 源码探究
- private Set<String> getAuthoritySet() {
- if (this.roles == null) {
- // 这里是从authentication 中获权限,在前面认证授权过程中我们将从数据查询道德角色权限信息通过自定义的LoginUser 设置到authentication 中,此时用户就能拿到对应的角色权限信息
-
- //JwtAuthenticationFilter 过滤器中将用户信息存入到SecurityContextHolder
- // UsernamePasswordAuthenticationToken authenticationToken = new //UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
- // SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- Collection<? extends GrantedAuthority> userAuthorities = this.authentication.getAuthorities();
- if (this.roleHierarchy != null) {
- userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
- }
- //转换为set集合
- this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
- }
-
- return this.roles;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
通过上面的源码我们知道 只需要在鉴权的时候返回true或false 就能实现鉴权访问
自定义JackExpressionRoot
- @Component("ex")
- public class JackExpressionRoot {
- public final boolean hasAuthority(String authority) {
- LoginUser loginUser = (LoginUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- List<String> permissions = loginUser.getPermissions();
- return permissions.contains(authority);
- }
- }
我们只需要按照security 鉴权判断方式编写即可实现自定义鉴权
- //实现@加组件名称.方法名('权限编码或ROLE_角色编码')
- @PreAuthorize("@ex.hasAuthority('ROLE_ceo')")
- public String hello() {
- return "hello";
- }
在SecurityConfig 中我们也可以去自定义认证成功或失败,以及登出成功实现自定义处理
自定义认证成功处理MySuccessHandler
- @Component
- public class MySuccessHandler implements AuthenticationSuccessHandler {
- @Override
- public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
- AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
- }
-
- @Override
- public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
- System.out.println("认证成功调用");
- }
- }
自定义认证失败处理
- @Component
- public class MyFailureHandler implements AuthenticationFailureHandler {
- @Override
- public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
- System.out.println("认证失败的时候调用");
- }
- }
自定义登出成功处理MyLogoutSuccessHandler
- @Component
- public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
- @Override
- public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
- System.out.println("注销成功时调用");
- }
- }
将自定义的组件配置到SecurityConfig中
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private MySuccessHandler successHandler;
- @Autowired
- private MyFailureHandler failureHandler;
- @Autowired
- private MyLogoutSuccessHandler logoutSuccessHandler;
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.formLogin()
- // 认证成功 调用
- .successHandler(successHandler)
- // 认证失败调用
- .failureHandler(failureHandler)
- .and()
- // 登出成功调用
- .logout().logoutSuccessHandler(logoutSuccessHandler)
- .and().authorizeRequests().anyRequest().authenticated();
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
验证码应该时在登录之前进行验证,因此可以通过过滤器实现
- @Component
- public class CaptchaFilter extends OncePerRequestFilter {
- //保存验证码的方式 redis session
- @Value("${application.captcha.type}")
- private String saveType;
- @Autowired
- private RedisUtil redisUtil;
- @Autowired
- private LoginFailureHandler failureHandler;
- @Autowired
- private LoginSuccessHandler successHandler;
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- String url = request.getRequestURI();
- if ("/login".equals(url) && "POST".equalsIgnoreCase(request.getMethod())) {
- //校验验证码
- try{
- validate(request);
- }catch (CaptchaException e) {
- //失败单独处理交给认证失败处理器
- failureHandler.onAuthenticationFailure(request, response, e);
- }
- }
- // 验证通过放行
- filterChain.doFilter(request, response);
- }
- public void validate (HttpServletRequest request) {
- //form提交的code
- String uuid = request.getParameter("uuid");
- String code = request.getParameter("code");
- String key = "";
- if (StrUtil.isEmpty(code) || StrUtil.isEmpty(uuid)) {
- throw new CaptchaException("验证码错误");
- }
- if("SESSION".equals(saveType)){
- key = CommonConstant.API_USER_IMAGE_CODE_KEY+saveType+uuid;
- Object redisCode = request.getSession().getAttribute(key);
- if (StrUtil.isBlankIfStr(redisCode) || !(redisCode).equals(code)) {
- throw new CaptchaException("验证码错误");
- }
- // 一次性使用验证完成删除
- request.getSession().removeAttribute(key);
- }else{
- key = CommonConstant.API_USER_IMAGE_CODE_KEY+saveType+uuid;
- Object redisCode = redisUtil.get(key);
- if (StrUtil.isBlankIfStr(redisCode) || !(redisCode).equals(code)) {
- throw new CaptchaException("验证码错误");
- }
- // 一次性使用验证完成删除
- redisUtil.del(key);
- }
-
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
也可以通过继承BasicAuthenticationFilter 实现jwt 认证过滤器
- public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
- @Autowired
- private JwtUtil jwtUtil;
- @Autowired
- private UserDetailServiceImp userDetailsService;
- @Autowired
- private ISysUserService sysUserService;
- public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
- super(authenticationManager);
- }
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
- String header = request.getHeader(jwtUtil.getHeader());
- if (StrUtil.isBlankOrUndefined(header)) {
- chain.doFilter(request, response);
- return;
- }
- Claims claims = jwtUtil.getClaimsByToken(header);
- if (claims == null) {
- throw new JwtException("token异常");
- }
- if (jwtUtil.isTokenExpired(claims)) {
- throw new JwtException("token已过期");
- }
- // 获取用户名
- String username = claims.getSubject();
- //获取用户权限信息
- SysUser sysUser = sysUserService.getByUsername(username);
- if (sysUser != null) {
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailsService.getUserGrantedAuthority(sysUser.getId(), username));
- SecurityContextHolder.getContext().setAuthentication(token);
- } else {
- // 将token存入Authentication中
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, null);
- SecurityContextHolder.getContext().setAuthentication中(token);
- }
- chain.doFilter(request, response);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private MySuccessHandler successHandler;
- @Autowired
- private MyFailureHandler failureHandler;
- @Autowired
- private MyLogoutSuccessHandler logoutSuccessHandler;
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.formLogin()
- // 认证成功 调用
- .successHandler(successHandler)
- // 认证失败调用
- .failureHandler(failureHandler)
- .and()
- // 登出成功调用
- .logout().logoutSuccessHandler(logoutSuccessHandler)
- .and().authorizeRequests().anyRequest().authenticated()
- .addFilter(jwtAuthenticationFilter())
- // 在UsernamePasswordAuthenticationFilter 先执行验证码过滤器
- .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
-
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。