springsecurity的学习(三):自定义的认证

简介

使用springsecurity的基本流程,原理,以及简单的自定义认证的例子

前后端登录校验流程

  1. 前端携带用户名密码访问登录接口
  2. 后端会和数据库中的用户名和密码进行校验
  3. 如果正确,使用用户名或id生成一个jwt,响应给前端
  4. 登录后前端访问其他请求需要在请求头中携带token
  5. 后端获取请求头中的token进行解析,获取userid
  6. 根据用户的id获取用户的相关信息,如果有权限则允许访问相关资源
  7. 访问资源后返回响应给前端

springsecurity原理

springsecurity的原理是一个过滤器链,包含了提供各种功能的过滤器,比较核心的有:

  1. UsernamePasswordAuthenticationFilter:负处理登录页面填写了用户名和密码后的登录请求,即负责用户认证的过滤器。
  2. ExceptionTranslationFiter:处理过滤器链中抛出的异常。
  3. FilterSecurityInterceptor:负责权限校验的过滤器。

认证原理

主要接口:

  1. Authentication接口:其实现类对象封装了用户的相关信息,表示当前访问系统的用户。
  2. AuthenticationManager接口:定义了认证authentication的方法
  3. UserDetailsService接口:加载用户特定数据的核心接口。定义了一个根据用户名查询用户信息的方法。
  4. UserDetails接口:包含用户的核心信息(权限等),从UserDetailsService根据用户名获取到的用户信息会封装成UserDetails对象返回,然后将这些信息设置到authenticaiton对象中。

解析:

用户输入用户名和密码首先会来到UsernamePasswordAuthenticationFilter,封装成Authentication对象,此时Authentication对象是没有权限信息的。

UserDetailsService接口的默认实现类是InMemoryUserDetailsManager,会根据用户名去内存中查询用户和用户对应的权限信息,然后把这些信息封装到UserDetails对象中,UserDetails对象的信息校验成功的话会设置到Authentication对象。

UsernamePasswordAuthenticationFilter最后会把通过层层过滤器返回的Authentication对象通过SecurityContextHolder().setAuthentication()封装起来,其他的过滤器要获得这个Authentication对象都是通过SecurityContextHolder来获取的。

自定义实现认证

登录接口中,通过providermanager的authentice方法进行认证,如果认证通过,使用用户id生成一个jwt,然后用户id作为key,用户信息作为value存入redis中。

需要自定义UserDetailsService接口的实现类,去数据库中查找用户信息。

自定义校验

定义jwt认证过滤器,获取token,解析token获得用户id,封装authentication对象存入到SecurityContextHolder。

前端携带token发起请求,会先到jwt认证过滤器

自定义认证的代码实现

UserDetailsServiceImpl类的编写

编写一个UserDetailsServiceImpl类实现UserDetailsService,去数据库中查找用户信息,结果封装成自定义的UserDetails对象

java 复制代码
package com.springSecurityTest.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.springSecurityTest.mapper.UserMapper;
import com.springSecurityTest.common.LoginUser;
import com.springSecurityTest.common.User;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Objects;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUserName,username);
        User user = userMapper.selectOne(lambdaQueryWrapper);
        if (Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        return new LoginUser(user);
    }
}

LoginUser类的编写

UserDetailsServiceImpl类的loadUserByUsername返回的是一个UserDetails对象,编写一个LoginUser类实现UserDetails接口。

java 复制代码
package com.springSecurityTest.common;

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.ArrayList;
import java.util.Collection;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
    private User user;
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.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;
    }
}

这样,就可以实现去数据库中查找用户的信息了,springboot发现这些接口的实现类,会自动的去调用自定义的实现类。
注意:此时如果直接输入密码,是不能通过认证的,springsecurity会使用一个默认的passwordencoder来进行密码校验,会对密码进行编码解码。要在数据库存储的密码前加上{noop}

securityConfig类

在数据库中是不会存储明文密码的,springsecurity中默认使用passwordEncoder,一般是不使用这个的,而是使用BCryptPasswordEncoder。需要编写一个配置类继承WebSecurityConfigurerAdapter ,写passwordEncoder方法返回BCryptPasswordEncoder

java 复制代码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
 }

BCryptPasswordEncoder的主要方法:

  • encode:传入密码的原文,会进行加密,返回字符串
  • matches:进行密码的校验,传入登录输入的密码和数据库中加密的密码进行比较。

扩展

encode方法就算原文相同,每次的调用返回的字符串也是不同的。在加密的时候,会生成一个随机的盐,然后盐和原文处理后再进行加密的。是非对称加密,只能加密不能解密。

注册的时候调用这个方法把用户输入的密码原文加密后存入数据库。

jwt的工具类

在自定义登录接口的时候,调用providerManager的方法进行认证,通过认证要生成jwt,然后把jwt放入到redis中。

使用jwt要导入相关的jwt依赖,以下是jwt工具类

java 复制代码
package com.springSecurityTest.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;// 60 * 60 *1000  一个小时
    //设置秘钥明文, 注意长度必须大于等于6位
    public static final String JWT_KEY = "huanfqc";

    public static String getUUID(){
        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("huanf")     // 签发者
                .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();
    }

}

主要方法:

  • createJWT:生成jwt
  • parseJWT:解析jwt

登录接口的编写

思路

springsecurity会对这个登录接口放行,让用户可以不登录访问这个接口。

接口中通过authenticationmanager的authenticate方法进行用户认证,需在securityconfig中配置把authenticationmanager注入容器。

认证成功生成一个jwt放入响应中返回,且把用户信息存入redis,可以让用户的请求通过jwt识别出用户信息。

loginController 类
java 复制代码
package com.springSecurityTest.controller;

import com.springSecurityTest.common.ResponseResult;
import com.springSecurityTest.common.User;
import com.springSecurityTest.service.LoginService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class loginController {
    @Resource
    private LoginService loginService;
    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user){
        return loginService.login(user);
    }
    @RequestMapping("/user/logout")
    public ResponseResult loginout(){
        return loginService.loginout();
    }
}
LoginService接口
java 复制代码
package com.springSecurityTest.service;

import com.springSecurityTest.common.ResponseResult;
import com.springSecurityTest.common.User;

public interface LoginService {
    ResponseResult login(User user);
    ResponseResult loginout();
}
LoginServiceImpl实现类
java 复制代码
package com.springSecurityTest.service.impl;

import com.baomidou.mybatisplus.extension.api.R;
import com.springSecurityTest.common.LoginUser;
import com.springSecurityTest.common.ResponseResult;
import com.springSecurityTest.common.User;
import com.springSecurityTest.service.LoginService;
import com.springSecurityTest.utils.JwtUtil;
import com.springSecurityTest.utils.RedisCache;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import sun.rmi.runtime.Log;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service
public class LoginServiceImpl implements LoginService {
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private RedisCache redisCache;
    @Override
    public ResponseResult login(User user) {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
        //用户认证
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        //认证没有通过,抛出异常
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        //认证通过,使用userid生成一个jwt,存入responseResult返回
        LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        //把用户信息存入redis,userid作为key
        redisCache.setCacheObject("token:"+userId,loginUser);
        return new ResponseResult(200,"success login",map);
    }

    @Override
    public ResponseResult loginout() {
    //获取Securitycontextholder中的用户id
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser =(LoginUser) usernamePasswordAuthenticationToken.getPrincipal();
        Long userid = loginUser.getUser().getId();
        //删除redis中的值
        redisCache.deleteObject("token:"+userid);
        return new ResponseResult(200,"用户注销成功");
    }
}

这个实现类中的AuthenticationManager 要想从容器中获取,需要在securityconfig类中重写authenticationmanagerBean方法

securityconfig类

重写securityconfig的configure方法,让外部可以不登录访问/user/login接口

java 复制代码
@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是失效的,我们就不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //指定让spring security放行登录接口的规则
                .authorizeRequests()
                // 对于登录接口 anonymous表示允许匿名访问
                .antMatchers("/user/login").anonymous()
                .antMatchers("/dark").hasAuthority("system:test:list")
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }
        @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
 }
redisCache工具类
java 复制代码
package com.springSecurityTest.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
//redis工具类
public class RedisCache {
    @Autowired
    public 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)
    {
        ValueOperations<String, 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 List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, 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)
    {
        HashOperations<String, 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> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

校验

思路

定义jwt认证过滤器,获取token,解析token并获取其中的userid,从redis中获取用户信息,存入securityContextHolder中。

JwtAuthenticationTokenFilter过滤器

继承OncePerRequestFilter,OncePerRequestFilter表示每次的Request都会进行拦截,不管是资源的请求还是数据的请求

java 复制代码
package com.springSecurityTest.filter;

import com.springSecurityTest.common.LoginUser;
import com.springSecurityTest.utils.JwtUtil;
import com.springSecurityTest.utils.RedisCache;
import io.jsonwebtoken.Claims;
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 sun.plugin.liveconnect.SecurityContextHelper;

import javax.annotation.Resource;
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;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Resource
    private RedisCache redisCache;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    //获取token
        String token = httpServletRequest.getHeader("token");
        //token为空直接放行
        if (!StringUtils.hasText(token)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        String userId;
        try {
            //解析token
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        String redisKey = "token:" + userId;
        //从redis中获取用户信息
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //这步本来是要给authentication封装权限信息的
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null, null);
        //存入securityContextHolder中
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //放行
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}
SecurityConfig类

定义的过滤器还要放到过滤器链中,在springsecurity的配置类中配置,在configure方法中添加

java 复制代码
package com.springSecurityTest.config;

import com.springSecurityTest.filter.JwtAuthenticationTokenFilter;
import com.springSecurityTest.handler.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Resource
    private AuthenticationEntryPointImpl authenticationEntryPoint;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
//testgit
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //由于是前后端分离项目,所以要关闭csrf
                .csrf().disable()
                //由于是前后端分离项目,所以session是失效的,我们就不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //指定让spring security放行登录接口的规则
                .authorizeRequests()
                // 对于登录接口 anonymous表示允许匿名访问
                .antMatchers("/user/login").anonymous()
                .antMatchers("/dark").hasAuthority("system:test:list")
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
        //配置异常处理器到过滤器链中
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);
http.cors();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

退出登录

获取securitycontextholder中的认证信息,删除redis中对应的数据即可。之前的代码中已经写了的。

在loginuser类中的loginout接口。

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习