Spring Security入门01-22 登录验证功能

课程链接:SpringSecurity框架教程
开始时间:2022-07-17

快速入门

搭建一个Spring Boot项目

添加基础依赖和创建启动类和controller
controller

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }
}

启动类

package com.bupt.security_test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SecurityTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityTestApplication.class, args);
    }

}

此时访问页面localhost:8080/hello
可以看到显示hello这一个单词

引入Security的依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

此时我们再去访问
localhost:8080/hello
会自动跳转到security带的登录界面
在这里插入图片描述
登录名默认是user
密码会在控制台打印给你
在这里插入图片描述
如果输入错误的账户密码
在这里插入图片描述
输入正确
在这里插入图片描述
默认有一个退出的接口logout
在这里插入图片描述

认证

登录校验流程图

在这里插入图片描述

Spring Security流程

本质是一个过滤器链
在这里插入图片描述

  • UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

  • ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。 发现异常并重定向

  • FilterSecurityInterceptor:负责权限校验的过滤器。(他实现了过滤器接口)

没想到还有重温之前学拦截器的部分,参考博客

这里只是选了三个典型的,还有其他过滤器,暂不研究
我们可以查看有哪些过滤器
在这里插入图片描述

认证流程详解

在这里插入图片描述

区分一下概念

  • Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

  • AuthenticationManager接口:定义了认证Authentication的方法

  • UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

  • UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

  • 第一步:提交用户名和密码 传入到UsernamePasswordAuthenticationFilter,这个过滤器把用户名和密码封装为一个Authentication对象,此时还没有权限信息

  • 第二步 调用authenticate方法进行认证,链条走到AuthenticationManager,但他也没完,继续调用DaoAuthenticationProvider的authenticate方法进行认证

  • 第三步 即使已经经过了三个过滤器,还是没有认证,需要继续调用loadUserByUsername方法查询用户,走到第四个链条处了

  • 第四步 根据用户名去查询用户及该用户对应的权限信息,InMemoryUserDetailsManager是在内存中查找的,并把对应用户信息添加上权限信息封装成UserDetails对象进行返回

  • 第五步 UserDetails返回到Provider处,判断PasswordEncoder对比UserDetails中的密码和Authentication的密码是否正确,如果正确就把UserDetails中的权限信息设置到Authentication对象中

  • 第六步 返回Authentication对象到第一个过滤器处,如果成功返回,就使用SecurityContextHolder.getContext().setAuthentication方法村吃该对象,其他过滤器可以通过SecurityContextHolder来获取当前用户信息

我们想想,在第四步中,查询的信息是在内存中查的,而我们需要从数据库查,那就得自己来实现这个接口

在这里插入图片描述
之后再请求
在这里插入图片描述
那么我们经过JWT拿到UserID后,怎么获取完整信息呢?再去数据库一条条查?会增大IO开销
因此,Redis闪亮登场。
那JWT要去Redis里面查,那总得Redis里面有东西吧,什么时候放呢?就在我们登录接口那里,如果认证通过,生成一个JWT的同时,再把UserID:用户信息存入Redis里面即可

解决问题

思路分析

  • 登录
    ①自定义登录接口
    调用ProviderManager的方法进行认证 如果认证通过生成jwt
    把用户信息存入redis中
    ②自定义UserDetailsService
    在这个实现类中去查询数据库

  • 校验:
    ①定义Jwt认证过滤器
    获取token
    解析token获取其中的userid
    从redis中获取用户信息
    存入SecurityContextHolder

准备工作,添加所需要的Maven依赖以及相应的工具类
在这里插入图片描述

功能实现

数据库校验用户

我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
建表并引入MybatisPuls和mysql驱动的依赖

CREATE TABLE `sys_user` (
  `id` BIGINT(20) 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 '用户类型(0管理员,1普通用户)',
  `create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  `update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',
  `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
  `del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

配置数据库信息
配置mapper

package com.bupt.security_test.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bupt.security_test.domain.User;

public interface UserMapper extends BaseMapper<User> {
}

在主启动类上扫描mapper

@MapperScan("com.bupt.security_test.mapper")
public class SecurityTestApplication 

测试一下

@SpringBootTest
public class SecurityTestApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testUserMapper() {
        List<User> users = userMapper.selectList(null);
        System.out.println(users);
    }
}

读取到了数据库数据,说明MyBatisPlus没问题

那我们就要尝试链接自己的数据库的账号密码了
创建一个类实现UserDetailsService接口,重写其中的方法。
这里的==@service注解不能忘记==

package com.bupt.security_test.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bupt.security_test.domain.LoginUser;
import com.bupt.security_test.domain.User;
import com.bupt.security_test.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 {
    @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);
    }
}

返回的实体类LoginUser我们也定义一下,因为我们的User本身和UserService没关系,需要借助LoginUser包装一下
注意后面几个方法虽然没实现,但返回改为了true而不是默认的false

package com.bupt.security_test.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
@NoArgsConstructor
@AllArgsConstructor
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;
    }
}

测试要求输入账号密码
在这里插入图片描述
现阶段要把password密码明文前面加上{noop}
不然识别不出来
这个括号里面是说明该密码采取什么方式进行的加密,{noop}表示没有加密
为什么呢,我们再看看上面的图
在这里插入图片描述

实际项目中我们不会把密码明文存储在数据库中。
​ 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。

我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。
我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。

配置一下SecurityConfig

package com.bupt.security_test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

写一个测试类看看

    @Test
    public void TestBCryptPasswordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode1 = bCryptPasswordEncoder.encode("1234");
        String encode2 = bCryptPasswordEncoder.encode("1234");
        System.out.println(encode1);
        System.out.println(encode2);
    }

我们打印输出,会发现每次encode的结果都不一致

$2a$10$NMAeABItflg2UyrWNuOwvu0hFhEqGIk.zyx0RUJbeAm6jrjQq54oi
$2a$10$p5J1O.5THmLqv9ATSv/juep3QxhsUOyGTh/5bL51.8kcwKMNNYlle

我们存数据库的密码不是明文,而是encode后的结果
但不管怎样,用谁加密,对应匹配他还是认识
再看一看

    @Test
    public void TestBCryptPasswordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode1 = bCryptPasswordEncoder.encode("1234");
        String encode2 = bCryptPasswordEncoder.encode("1234");
        System.out.println("encode1输出" + encode1);
        System.out.println("encode1判断是否能匹配rawPassword" + bCryptPasswordEncoder.matches("1234", encode1));
        System.out.println("encode2输出" + encode2);
        System.out.println("encode2判断是否能匹配rawPassword" + bCryptPasswordEncoder.matches("1234", encode2));
    }

输出

encode1输出$2a$10$PJNq2HpUoHdj52zp3dPdNO9wKTpuGJHYeF.nX.NyDjt8jWUBqRM46
encode1判断是否能匹配rawPasswordtrue
encode2输出$2a$10$WAwFkHZ8xWoR3e5D9zkWgefxIrrRsZv8B093x9FYA7I/jCqMw8mvi
encode2判断是否能匹配rawPasswordtrue

这里为什么不同的编码都能匹配上呢,知识超纲了,这好像是什么 盐 值

配置好之后,我们直接输入用户密码就不行了,要更新数据库的密码为加密后的密码才行

JWT工具类

package com.bupt.security_test.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;

/**
 * JWT工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "bupt";

    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();
    }

    /**
     * 生成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 (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("jdh")     // 签发者
                .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 jwt = createJWT("2123");
        System.out.println(jwt);
        //base64解码
        Claims claims = parseJWT(jwt);
        System.out.println(claims.getSubject());
        //String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
        //Claims claims = parseJWT(token);
        //System.out.println(claims);
    }

    /**
     * 生成加密后的秘钥 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();
    }

}

执行主方法得到

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJiMGQxYmNjODI0NzI0ODljYjZlMDdiMGIxNDkyMzhkZiIsInN1YiI6IjIxMjMiLCJpc3MiOiJqZGgiLCJpYXQiOjE2NTgwNDM0ODQsImV4cCI6MTY1ODA0NzA4NH0.R4WV-QID5r2gACp7qXYV_fXy13VFAV0cjBzUfgFO-v8
2123

登录接口

自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt

自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。
认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。

首先说如何放行
SecurityConfig重写方法

 //用来放行,总不能登录界面都要让你认证吧
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }

然后正常的controller service serviceimpl走一遍

package com.bupt.security_test.controller;

import com.bupt.security_test.domain.ResponseResult;
import com.bupt.security_test.domain.User;
import com.bupt.security_test.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
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;

@RestController
public class LoginController {
    @Autowired
    private LoginService loginService;

    @PostMapping("/user/login")
    //RequestBody用来拿JSON传过来的用户名和密码
    public ResponseResult login(@RequestBody User user) {
        //登录
        return loginService.login(user);
    }
}
public interface LoginService {

    ResponseResult login(User user);
}
@Service
public class LoginServiceImpl implements LoginService {
    //这个类在SecurityConfig里面实现的
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;

    @Override

    public ResponseResult login(User user) {
        //AuthenticationManager authenticate进行用户认证
        //用户名和密码先封装,而Authentication是接口,我们需要找一个他的实现类
        //在接口名上 ctrl+alt+鼠标左键,可以看其常用实现类
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());

        //通过authenticationManager来实现认证,会调用UserDetailsServiceImpl
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        //如果认证没通过,给出对应的提示
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登录失败");
        }

        //如果认证通过了,使用userid生成一个jwt 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("login:" + userid, loginUser);
        return new ResponseResult(200, "登录成功", map);
    }
}

而我们需要先暴露AuthenticationManager
因此要在SecurityConfig添加

//重写方法,用来暴露AuthenticationManager
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

使用postman进行debug
在这里插入图片描述

debug看,

//通过authenticationManager来实现认证,会调用UserDetailsServiceImpl
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

拿到的 authenticate包含的principal下有user信息
在这里插入图片描述

集成redis后,我们再来看看测试结果
在这里插入图片描述
我们把这个token拿回工具类解析

Claims claim = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNWNkN2E1ZDhlYzg0Y2NiYWU5NmM3ZDliMjUyOWViOSIsInN1YiI6IjEiLCJpc3MiOiJqZGgiLCJpYXQiOjE2NTgwNDgyMjcsImV4cCI6MTY1ODA1MTgyN30.8yp6VBEHL2aAB5dTlsG13lsRg_XNlNmVNZ88zumhT4c");
        System.out.println(claim.getSubject());

得到的输出为1,即该用户的id为1

认证过滤器

校验:

①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder

过滤器代码

package com.bupt.security_test.filter;

import com.bupt.security_test.domain.LoginUser;
import com.bupt.security_test.utils.JwtUtil;
import com.bupt.security_test.utils.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

//认证过滤器,那这个过滤器需要在SecurityConfig里面配置他出现的位置
//他要出现在UsernamePasswordAuthenticationFilter之前
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token,从请求头里面拿
        String token = request.getHeader("token");
        //没有token,直接放行
        //放行后会去执行后面的过滤器
        //走完后面的过滤器,响应回来的时候还会走一遍过滤器链
        //如果没有return,回来的时候还会执行一次,就会报错,因为根本没有token
        //登录接口也会从这里过,但因为没有token就被放行了
        if (!StringUtils.hasText(token)) {
            filterChain.doFilter(request, response);
            return;
        }
        String userid;
        //解析token
        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);

        //存入SecurityContextHolder
        if (Objects.isNull(loginUser)) {
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中

        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

配置过滤器位置
SecurityConfig

//添加过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

测试一下,发送post请求
在这里插入图片描述
debug发现在

if (!StringUtils.hasText(token)) {
            filterChain.doFilter(request, response);
            return;
        }

直接就放行了
然后一直走,完成登录后就返回token
此时我们再来测试一下hello接口
在这里插入图片描述
被拦截下来了,需要补充token信息
在这里插入图片描述
这样就能访问了

退出登录

清空redis SecurityContextHolder

  //退出登录
    @RequestMapping("/user/logout")
    public ResponseResult logout() {
        return loginService.logout();
    }

实现类

    //退出登录
    @Override
    public ResponseResult logout() {
        //获取SecurityContextHolder中的用户id
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        Long userid = loginUser.getUser().getId();
        //用户如果是未登录状态发起退出请求会被拦截下来,根本到不了这个方法
        //删除redis中的值
        redisCache.deleteObject("login:" + userid);
        return new ResponseResult(200, "注销成功");
    }

在这里插入图片描述
此时我们再使用原来的token访问就不行
结束时间:2022-07-17

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值