做网站实训心得体会,谷歌怎么投放广告,网站切图是指什么,学校教务网站的设计与实现目录
一. 快速入门
二. 认证
2.1 登陆校验流程
2.2 原理初探
2.3 解决问题
2.3.1 思路分析
2.3.2 准备工作
2.3.3 实现
2.3.3.1 数据库校验用户
2.3.3.2 密码加密存储
2.3.3.3 登录接口
2.3.3.4 认证过滤器
2.3.3.5 退出登录 Spring Security是Spring家族中的一个…目录
一. 快速入门
二. 认证
2.1 登陆校验流程
2.2 原理初探
2.3 解决问题
2.3.1 思路分析
2.3.2 准备工作
2.3.3 实现
2.3.3.1 数据库校验用户
2.3.3.2 密码加密存储
2.3.3.3 登录接口
2.3.3.4 认证过滤器
2.3.3.5 退出登录 Spring Security是Spring家族中的一个安全管理框架相比与另外一个安全框架Shiro它提供了更丰富的功能社区资源也比Shiro丰富。
一般来说大型项目用Spring Security比较多小项目用Shiro比较多因为相比于Spring SecurityShiro上手比较简单。
一般Web应用需要进行认证和授权。
认证验证当前访问系统的是不是本系统用户并且要确认具体是哪个用户授权经过认证后判断当前用户是否有权限进行某个操作
而认证和授权正是Spring Security作为安全框架的核心功能
一. 快速入门
我们先简单构建出一个SpringBoot项目。 这个时候我们访问我们写的一个简单的hello接口验证是否构建成功。 接着引入SpringSecurity。 这个时候我们再看看访问接口的效果。 引入了SpringSecurity之后访问接口会自动跳转到一个登录页面默认的用户名是user密码会输出到控制台必须登录后才能对接口进行访问。 二. 认证
2.1 登陆校验流程
首先我们要先了解登录校验流程首先前端携带用户名和密码访问登录接口服务器拿到这个用户名和密码之后去和数据库中的进行比较如果正确使用用户名/用户ID生成一个jwt接着把jwt响应给前端之后登录后访问其他的请求都会在请求头中携带token服务器每次获取请求头中的token进行解析、获取UserID根据用户名id获取用户相关信息查看器权限如果有权限则响应给前端。
2.2 原理初探
SpringSecurity的原理其实就是一个过滤器链内部提供了各种功能的过滤器这里我们先看看上方快速入门中涉及的过滤器。
UsernamePasswordAuthenticationFilter负责处理在登录页面填写的用户名密码后的登录请求ExceptionTranslationFilter处理过滤器链中抛出的任何AccessDeniedException和AuthenticationExceptionFilterSecurityInterceptor负责权限校验的过滤器
我们也可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器以及顺序。 接下来我们来看看认证流程图的解析。 这里我们只需要能看懂其过程即可简单来说就是 用户提交了用户名和密码UsernamePasswordAuthenticationFilter将其封装未Authentication对象并且调用authenticate方法进行认证接着在调用DaoAuthenticationProvider的authenticate方法进行认证再调用loadUserByUserName方法查询用户这里的查询是在内存中进行查找然后将对应的用户信息封装未UserDetails对象通过PasswordEncoder对比UserDetails中的密码和Authentication的密码是否正确如果正确就把UserDetails中的权限信息设置到Authentication对象中接着返回Authentication对象最后使用SecurityContextHolder.getContext().setAuthentication方法存储该对象其他过滤器会通过SecurityContextHoder来获取当前用户信息。这一段不用记忆能听懂即可 那么我们知道了其过程才能对其进行修改首先这里的从内存中查找我们肯定是要该为从数据库中查找这里需要我们自定义一个UserDetailsService的实现类并且也不会使用默认的用户名密码登录界面也一定是自己编写的不需要用他提供的默认登录页面。
基于我们分析的情况可以得到这样的一张图。 这个时候就返回了一个jwt给前端而这时前端进行的其他请求都会携带token那么我们第一步就需要先校验是否携带token并且解析token获取对应的userid并且将其封装为Anthentication对象存入SecurityContextHolder为了其他过滤器可以拿到。 那么这里还有一个问题从jwt认证过滤器中取到了userid后如何获取完整的用户信息
这里我们使用redis当服务器认证通过使用用户id生成jwt给前端的时候以用户id作为key用户的信息作为value存入redis之后就可以通过userid从redis中获取到完整的用户信息了。 2.3 解决问题
2.3.1 思路分析
从上述的原理初探中我们也大概分析出了我们要是自己实现前后端分离的认证流程需要做的事情。
登录 a.自定义登录接口 调用ProviderManager的方法进行认证如果认证通过生成jwt 把用户信息存入redis中 b.自定义UserDetailsService 在这个实现类中去查询数据库
校验 a.自定义jwt认证过滤器 获取token 解析token获取其中userid 从redis中获取完整用户信息 存入SecurityContextHolder
2.3.2 准备工作
首先需要添加对应的依赖 !-- SpringSecurity启动器 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependency!-- redis依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- fastjson依赖 --dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.33/version/dependency!-- jwt依赖 --dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.0/version/dependency
接着我们需要用到Redis需要加入Redis相关的配置
首先是FastJson的序列化器
package org.example.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;import java.nio.charset.Charset;/*** Redis使用fastjson序列化* param T*/
public class FastJsonRedisSerializerT implements RedisSerializerT {public static final Charset DEFAULT_CHARSET Charset.forName(UTF-8);private ClassT clazz;static {ParserConfig.getGlobalInstance().setAutoTypeSupport(true);}public FastJsonRedisSerializer(ClassT clazz){super();this.clazzclazz;}Overridepublic byte[] serialize(T t) throws SerializationException {if (t null){return new byte[0];}return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}Overridepublic T deserialize(byte[] bytes) throws SerializationException {if (bytesnull || bytes.length0){return null;}String str new String(bytes,DEFAULT_CHARSET);return JSON.parseObject(str,clazz);}protected JavaType getJavaType(Class? clazz){return TypeFactory.defaultInstance().constructType(clazz);}}
创建RedisConfig在其中创建序列化器解决乱码等问题
package org.example.config;
import org.example.utils.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;Configuration
public class RedisConfig {BeanSuppressWarnings(value {unchecked,rawtypes})public RedisTemplateObject,Object redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplateObject,Object template new RedisTemplate();template.setConnectionFactory(connectionFactory);FastJsonRedisSerializer serializer new FastJsonRedisSerializer(Object.class);//使用StringRedisSerializer来序列化和反序列化redus的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);template.afterPropertiesSet();return template;}
} 还需要统一响应类
package org.example.domain;
import com.fasterxml.jackson.annotation.JsonInclude;
JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResultT{/*** 状态码*/private Integer code;/*** 提示信息如果有错误时前端可以获取该字段进行提示*/private String msg;/*** 查询到的结果数据*/private T data;public ResponseResult(Integer code,String msg){this.code code;this.msg msg;}public ResponseResult(Integer code,T data){this.code code;this.data data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg msg;}public T getData() {return data;}public void setData(T data) {this.data data;}public ResponseResult(Integer code,String msg,T data){this.code code;this.msg msg;this.data data;}
}再需要jwt的工具类用于生成jwt以及对jwt进行解析
package org.example.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;public class JwtUtil {//有效期为public static final Long JWT_TTL 60*60*1000L; //一个小时//设置密钥明文public static final String JWT_KEY hzj;public static String getUUID(){String token UUID.randomUUID().toString().replaceAll(-,);return token;}/*** 生成jwt* param subject token中要存放的数据json格式* return*/public static String createJWT(String subject){JwtBuilder builder getJwtBuilder(subject,null,getUUID()); //设置过期时间return builder.compact();}/*** 生成jwt* 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(ttlMillisnull){ttlMillisJwtUtil.JWT_TTL;}long expMillis nowMillis ttlMillis;Date expDate new Date(expMillis);return Jwts.builder().setId(uuid) //唯一的Id.setSubject(subject) //主题 可以是Json数据.setIssuer(hzj) //签发者.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();}public static void main(String[] args) throws Exception{String token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTg0MjU5MzIsInVzZX
JJZCI6MTExLCJ1c2VybmFtZSI6Ik1hcmtaUVAifQ.PTlOdRG7ROVJqPrA0q2ac7rKFzNNFR3lTMyP_8fIw9Q;Claims claims parseJWT(token);System.out.println(claims);}/*** 生成加密后的密钥secretkey* return*/public static SecretKey generalkey(){byte[] encodeedkey Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key new SecretKeySpec(encodeedkey,0,encodeedkey.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();}
}再定义一个Redis的工具类RedisCache这样可以使我们调用redistemplate更加简单
package org.example.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;SuppressWarnings(value { unchecked, rawtypes })
Component
public class RedisCache
{Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象Integer、String、实体类等** param key 缓存的键值* param value 缓存的值*/public T void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象Integer、String、实体类等** param key 缓存的键值* param value 缓存的值* param timeout 时间* param timeUnit 时间颗粒度*/public T void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** param key Redis键* param timeout 超时时间* return true设置成功false设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** param key Redis键* param timeout 超时时间* param unit 时间单位* return true设置成功false设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。** param key 缓存键值* return 缓存键值对应的数据*/public T T getCacheObject(final String key){ValueOperationsString, T operation redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** param collection 多个对象* return*/public long deleteObject(final Collection collection){return redisTemplate.delete(collection);}/*** 缓存List数据** param key 缓存的键值* param dataList 待缓存的List数据* return 缓存的对象*/public T long setCacheList(final String key, final ListT dataList){Long count redisTemplate.opsForList().rightPushAll(key, dataList);return count null ? 0 : count;}/*** 获得缓存的list对象** param key 缓存的键值* return 缓存键值对应的数据*/public T ListT getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** param key 缓存键值* param dataSet 缓存的数据* return 缓存数据的对象*/public T BoundSetOperationsString, T setCacheSet(final String key, final SetT dataSet){BoundSetOperationsString, T setOperation redisTemplate.boundSetOps(key);IteratorT it dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** param key* return*/public T SetT getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** param key* param dataMap*/public T void setCacheMap(final String key, final MapString, T dataMap){if (dataMap ! null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** param key* return*/public T MapString, T getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** param key Redis键* param hKey Hash键* param value 值*/public T void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** param key Redis键* param hKey Hash键* return Hash中的对象*/public T T getCacheMapValue(final String key, final String hKey){HashOperationsString, String, T opsForHash redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 删除Hash中的数据** param key* param hkey*/public void delCacheMapValue(final String key, final String hkey){HashOperations hashOperations redisTemplate.opsForHash();hashOperations.delete(key, hkey);}/*** 获取多个Hash中的数据** param key Redis键* param hKeys Hash键集合* return Hash对象集合*/public T ListT getMultiCacheMapValue(final String key, final CollectionObject hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 获得缓存的基本对象列表** param pattern 字符串前缀* return 对象列表*/public CollectionString keys(final String pattern){return redisTemplate.keys(pattern);}
}
我们还有可能往响应中写入数据那么就还需要一个工具类WebUtils
package org.example.utils;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class WebUtils {/*** 将字符串渲染到客户端** param response 渲染对象* param string 待渲染的字符串* return null*/public static String renderString(HttpServletResponse response, String string) {try{response.setStatus(200);response.setContentType(application/json);response.setCharacterEncoding(utf-8);response.getWriter().print(string);}catch (IOException e){e.printStackTrace();}return null;}
}
最后写对应的用户实体类
package org.example.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/*** 用户表(User)实体类*/
Data
AllArgsConstructor
NoArgsConstructor
public class User implements Serializable {private static final long serialVersionUID -40356785423868312L;/*** 主键*/private Long id;/*** 用户名*/private String userName;/*** 昵称*/private String nickName;/*** 密码*/private String password;/*** 账号状态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 Date createTime;/*** 更新人*/private Long updateBy;/*** 更新时间*/private Date updateTime;/*** 删除标志0代表未删除1代表已删除*/private Integer delFlag;
} 根据我们上方的分析我们是需要自定义一个UserDetailsService让SpringSecuriry使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
我们先建立一个数据库表sys_user。
CREATE TABLE sys_user (id bigint NOT NULL AUTO_INCREMENT COMMENT 主键,user_name varchar(64) NOT NULL DEFAULT NULL COMMENT 用户名,nick_name varchar(64) NOT NULL DEFAULT NULL COMMENT 呢称,password varchar(64) NOT NULL DEFAULT NULL COMMENT 密码,status char(1) DEFAULT 0 COMMENT 账号状态0正常1停用),email varchar(64) DEFAULT NULL COMMENT 邮箱,phonenumber varchar(32) DEFAULT NULL COMMENT 手机号,sex char(1) DEFAULT NULL COMMENT 用户性别0男1女2未知),avatar varchar(128) DEFAULT NULL COMMENT 头像,user_type char(1) NOT NULL DEFAULT 1 COMMENT 用户类型O管理员1普通用户),create_by bigint DEFAULT NULL COMMENT 创建人的用户id,create_time datetime DEFAULT NULL COMMENT 创建时间,update_by bigint DEFAULT NULL COMMENT 更新人,update_time datetime DEFAULT NULL COMMENT 更新时间,del_flag int DEFAULT 0 COMMENT 删除标志O代表未删除1代表已删除),PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT3 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT用户表;接着引入myBatisPlus和mysql驱动。 dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.3/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId/dependency 接着配置数据库的相关信息。 接着定义mapper接口UserMapper使用mybatisplus加入对应的注解。
package org.example.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.example.domain.User;public interface UserMapper extends BaseMapperUser {
}
接着配置组件扫描
最后测试一下mp能否正常使用。
引入junit 这样就是可以正常使用了。
2.3.3 实现
2.3.3.1 数据库校验用户
接下来我们需要进行核心代码的实现。 首先我们先进行自定义UserDetailsService。
package org.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.example.domain.LoginUser;
import org.example.domain.User;
import org.example.mapper.UserMapper;
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 java.util.Objects;
Service
public class UserDetailsServiceImpl implements UserDetailsService {Autowiredprivate UserMapper userMapper;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息 [InMemoryUserDetailsManager是在内存中查找]LambdaQueryWrapperUser wrapper new LambdaQueryWrapper();wrapper.eq(User::getUserName,username);User user userMapper.selectOne(wrapper);//如果查询不到数据就抛出异常给出提示if(Objects.isNull(user)){throw new RuntimeException(用户名或密码错误!);}//TODO 查询权限信息//封装为UserDetails对象返回return new LoginUser(user);}
}
这里要将user封装为UserDetails进行返回。
package org.example.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
Data
AllArgsConstructor
NoArgsConstructor
public class LoginUser implements UserDetails {private User user;Overridepublic Collection? extends GrantedAuthority getAuthorities() {return null;}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;}
}
最后这里有一个点就是我们需要进行登录从数据库拿数据的测试需要往表中写入用户数据并且如果你想让用户的密码是明文传输需要在密码前加上{noop}。 这里就实现了输入数据库中的用户名密码进行登录了。 2.3.3.2 密码加密存储
这里说一下为什么要在密码前面加上{noop}因为默认使用的PasswordEncoder要求数据库中的密码格式为{id}password它会根据id去判断密码的加密方式但是我们一般不会采取这种方式所以就需要替换掉PasswordEncoder。 接下来我们进行测试看看。 可以看到我们这里传入的两次密码原文是一样的但是却得到了不同的结果这里其实和加盐算法有关之后我还会写一个自定义加密的文章。
得到加密之后的密码之后就可以将加密后的密码存入数据库之后可以由前端传过来的明文密码与数据库中的加密后的密码进行验证进行登录。
这个时候我们启动项目去登录发现之前的密码已经登不上了因为数据库此时存放的应该是注册阶段存入数据库的加密后的密码而不是原文密码了因为没注册我将加密后的密码自行写入数据库中。
2.3.3.3 登录接口
我们需要实现一个登录接口然后让SpringSecuruty对其进行放行如果不放行就自相矛盾了在接口中通过AuthenticationManager的authenticate方法来进行用户认证所以需要在SecurityConfig中配置把AuthenticationManager注入容器。
认证成功的话需要生成一个jwt放入响应中并且为了让用户下次请求时能通过jwt识别出具体是哪个用户需要把用户信息存入redis可以把用户id作为key。
先写LoginController 接着写对应的Service。
在SecurityConfig中进行AuthenticationManager的注入和登录接口的放行。 在service中的业务逻辑中如果认证失败则返回一个自定义异常但是如果认证成功我们需要如何获取到对应的信息呢。
这里我们可以debug看看得到的对象。 这里发现是Principal中可以得到对应需要的信息。
接着补全代码。 最后进行测试看看。 2.3.3.4 认证过滤器
我先将代码贴上。
Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {Autowiredprivate RedisCache redisCache;Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取tokenString token request.getHeader(token);if (!StringUtils.hasText(token)) {//放行filterChain.doFilter(request, response); //这里放行是因为还有后续的过滤器会给出对应的异常return; //token为空 不执行后续流程}//解析tokenString userid;try {Claims claims JwtUtil.parseJWT(token);userid claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(token非法!);}//从redis中获取用户信息String redisKey login: userid;LoginUser loginUser redisCache.getCacheObject(redisKey);if (Objects.isNull(loginUser)){throw new RuntimeException(用户未登录!);}//将信息存入SecurityContextHolder因为过滤器链后面的filter都是从中获取认证信息进行对应放行//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(loginUser,null,null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request,response); //此时的放行是携带认证的不同于上方token为空的放行}
}
首先这里获取token我们是从请求头中获取对应的token然后对其进行判空如果为空我们直接进行放行且不走后续流程接下来进行解析token得到里面的userid再根据userid从redis中获取对应的用户信息最后将其存储到SecurityContextHolder中因为后续的过滤器都需要从中获取日认证信息最后进行分析操作。 还有一个需要注意的点就是SecurityContextHolder.getContext().setAuthentication()需要传入authentication对象我们构建对象的时候采用的是三个参数的因为第三个参数是判断是否认证的关键。
接下来我们需要将这个过滤器进行配置。 接着我们进行访问user/login接口会返回给我们一个带token的响应体再访问hello接口此时是403的因为没有携带token所以就对应上方的代码没有token放行并且return不执行后续流程这里的放行是因为后续有其他专门抛异常的过滤器进行处理而return是为了不让其走响应的流程
此时若我们将user/login生成的token放入hello接口的请求头那么就可以正常访问到了。 那么我们这套过滤器的目的也就达到了获取token、解析token、存入SecurityContextHolder
2.3.3.5 退出登录
到这里我们也就比较容易的实现退出登录了我们只需要删除redis中对应的数据之后携带token进行访问的时候在我们自定义的过滤器中会获取redis中对应的用户信息此时获取不到就意味着未登录。 我们携带这个token去访/user/logout接口。 那么退出登录功能就实现了。
本文学习于b站up主三更