SpringSecurity+登錄功能+jwt校驗過濾器+redis配置 一、思路分析 1.登錄 ①自定義登錄介面 調用ProviderManager的方法進行認證 如果認證通過生成jwt 把用戶信息存入redis中 ②自定義UserDetailsService 在這個實現類中去查詢資料庫 註意配置 ...
SpringSecurity+登錄功能+jwt校驗過濾器+redis配置
一、思路分析
1.登錄
①自定義登錄介面
調用ProviderManager的方法進行認證 如果認證通過生成jwt
把用戶信息存入redis中
②自定義UserDetailsService
在這個實現類中去查詢資料庫
註意配置passwordEncoder為BCryptPasswordEncoder
2.校驗:
①定義Jwt認證過濾器
獲取token
解析token獲取其中的userid
從redis中獲取用戶信息
存入SecurityContextHolder
二、登錄介面代碼實現(第一次登陸獲取jwt)
1.業務代碼
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult login(User user) {
//1,使用springsecurity功能認證,把用戶名密碼存入令牌
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
//2.1,預設使用UserDetailService去記憶體中找user用戶,需要定義Impl實現類來重寫查詢方法,改成從資料庫查詢
//2.2,UserDetailServiceImpl從資料庫查詢出user返回到authenticate這裡。具體查看a類
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//2.3,判斷是否認證通過
if(Objects.isNull(authenticate)){
throw new RuntimeException("用戶名或密碼錯誤");
}
//3.1,獲取userid 生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
//3.2,生成jwt
String jwt = JwtUtil.createJWT(userId);
//3.3,把用戶信息存入redis
redisCache.setCacheObject("bloglogin:"+userId,loginUser);
//4.1,把token和userinfo封裝 返回
//4.2,把User轉換成UserInfoVo
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
BlogUserLoginVo vo = new BlogUserLoginVo(jwt,userInfoVo);
return ResponseResult.okResult(vo);
}
2.a類:UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據用戶名查詢用戶信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
//判斷是否查到用戶 如果沒查到拋出異常
if(Objects.isNull(user)){
throw new RuntimeException("用戶不存在");
}
//返回用戶信息
// TODO 查詢許可權信息封裝
return new LoginUser(user);
}
}
3.SecurityConfig配置類
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//關閉csrf
.csrf().disable()
//不通過Session獲取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 對於登錄介面 允許匿名訪問
.antMatchers("/login").anonymous()
// 除上面外的所有請求全部不需要認證即可訪問
.anyRequest().permitAll();
http.logout().disable();
//允許跨域
http.cors();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
三、登錄校驗過濾器代碼實現(校驗jwt)
1.登錄校驗過濾器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, IOException, ServletException {
//1,獲取請求頭中的token
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
//說明該介面不需要登錄直接放行,如果是第一次登陸的話跳轉到登陸去獲取token
filterChain.doFilter(request, response);
return;
}
//2,解析獲取userid
Claims claims = null;
try {
//String jwt = JwtUtil.createJWT(userId);jwt內容為id
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//token超時 token非法
//響應告訴前端需要重新登錄
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
String userId = claims.getSubject();
//3,從redis中獲取用戶信息
LoginUser loginUser = redisCache.getCacheObject("bloglogin:" + userId);
//如果獲取不到
if(Objects.isNull(loginUser)){
//說明登錄過期 提示重新登錄
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
//4,存入SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
//UPToken令牌存入Security上下文的設置身份驗證屬性中,後面過濾器會從Security上下文這裡獲取用戶信息
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
2.登錄校驗過濾器加入到過濾器組中
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//1,註入登錄校驗過濾器
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//關閉csrf
.csrf().disable()
//不通過Session獲取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 對於登錄介面 允許匿名訪問
.antMatchers("/login").anonymous()
//jwt過濾器測試用,如果測試沒有問題吧這裡刪除了
.antMatchers("/link/getAllLink").authenticated()
// 除上面外的所有請求全部不需要認證即可訪問
.anyRequest().permitAll();
http.logout().disable();
//***2,把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//允許跨域
http.cors();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
*** Redis使用FastJson序列化
package com.lwq.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
*
* @author sg
*/
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;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
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);
}
}
** RedisConfig Redis配置
package com.lwq.config;
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 {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
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;
}
}