Springsecurity笔记14-18章JWT+Spring Security+redis+mysql 实现认证【动力节点】

news2024/11/19 23:26:17

15 SpringSecurity 集成thymeleaf

此项目是在springsecurity-12-database-authorization-method 的基础上进行
复制springsecurity-12-database-authorization-method 并重命名为springsecurity-13-thymeleaf

15.1 添加thymeleaf依赖

|

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

15.2 修改application.yml

加入thymeleaf的配置

| spring:

thymeleaf:

cache: false # 不使用缓存check-template: true  # 检查thymeleaf模板是否存在 |

| — |

15.3 idea 添加thymeleaf模板

【File】—》【Settings…】

模板名称thymeleaf ,扩展名html,具体内容如下:

|

#[[$Title$]]#

#[[ E N D END END]]#

简要说明:

#[[ T i t l e Title Title]]# #[[ E N D END END]]# 这两处的作用是,当你新建一个模板页面时,在标签中输入标题内容后,只需要点击回车键,光标就会直接跳到内,省去了你挪动鼠标,或者挪动方向键的步骤,也可以给你节省一点点时间。<strong>新版****idea可能有点问题,实验一下Enable Live Template</strong>

15.4 新建LoginController

| @Controller

@RequestMapping(“/login”)

public class LoginController {

_/**

 * 跳转到登陆页面
 */

_@RequestMapping("/toLogin")

public String toLogin(){

    return "login";

}
}

15.5 创建thymeleaf文件login.html

在templates下面创建login.html,使用模板创建

|

<meta charset="UTF-8">

<title>用户登陆</title>

登录页面

<table>

    <tr>

        <td>用户名:</td>

        <td><input type="text" name="uname" value="thomas"></td>

    </tr>

    <tr>

        <td>密码:</td>

        <td><input type="password" name="pwd"></td>

    </tr>

    <tr>

        <td colspan="2">

            <button type="submit">登录</button>

        </td>

    </tr>

</table>

15.7 修改安全配置文件WebSecurityConfig

修改后如下:

| @EnableGlobalMethodSecurity(prePostEnabled = true)

//@Configuration

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean

public PasswordEncoder passwordEncoder(){

    return new BCryptPasswordEncoder();

}

@Override

protected void configure(HttpSecurity http) throws Exception {

    //设置登陆方式
    http.formLogin()//使用用户名和密码的登陆方式
            .usernameParameter("uname") //页面表单的用户名的name

            .passwordParameter("pwd")//页面表单的密码的name

            .loginPage("/login/toLogin") //自己定义登陆页面的地址
            .loginProcessingUrl("/login/doLogin")//配置登陆的url

            .successForwardUrl("/index/toIndex") //登陆成功跳转的页面
            .failureForwardUrl("/login/toLogin")//登陆失败跳转的页面
            .permitAll(); //放行和登陆有关的url,别忘了写这个
    //配置退出方式
    http.logout()

            .logoutUrl("/logout")

            .logoutSuccessUrl("/login/toLogin")

            .permitAll();/放行和退出有关的url,别忘了写这个


    //配置路径拦截 的url的匹配规则
    http.authorizeRequests()

            //任何路径要求必须认证之后才能访问
            .anyRequest().authenticated();

    // 禁用csrf跨站请求攻击  后面可以使用postman工具测试,注意要禁用csrf
    http.csrf().disable();

}
}

15.8 创建IndexController

| @Controller

@RequestMapping(“/index”)

public class IndexController {

_/**

 * 登录成功后进入主页
 */

_@RequestMapping("/toIndex")

public String toIndex(){

    return "main";

}
}

15.9 创建thymeleaf文件main.html

在templates下面创建main.html

|

<meta charset="UTF-8">

<title>系统首页</title>

系统首页

查询学生


添加学生


更新学生


删除学生


导出学生





退出


15.10 修改Studentcontroller

修改后如下:

| @Controller

@Slf4j

@RequestMapping(“/student”)

public class StudentController {

@GetMapping("/query")

@PreAuthorize("hasAuthority('student:query')")

public String queryInfo(){

    return "user/query";

}

@GetMapping("/add")

@PreAuthorize("hasAuthority('student:add')")

public String addInfo(){

    return "user/add";

}

@GetMapping("/update")

@PreAuthorize("hasAuthority('student:update')")

public String updateInfo(){

    return "user/update";

}

@GetMapping("/delete")

@PreAuthorize("hasAuthority('student:delete')")

public String deleteInfo(){

    return "user/delete";

}

@GetMapping("/export")

@PreAuthorize("hasAuthority('student:export')")

public String exportInfo(){

    return "/user/export";

}
}

15.11 在templates/user下面创建学生管理的各个页面

创建export.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>系统首页-学生管理</title>
</head>
<body>
<h1 align="center">系统首页-学生管理-导出</h1>
<a href="/index/toIndex">返回</a>
<br>
</body>
</html> 

创建query.html

 <!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>系统首页-学生管理</title>

</head>

<body>

<h1 align="center">系统首页-学生管理-查询</h1>

<a href="/index/toIndex">返回</a>

<br>

</body>

</html> 

创建add.html

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>系统首页-学生管理</title>

</head>

<body>

<h1 align="center">系统首页-学生管理-新增</h1>

<a href="/index/toIndex">返回</a>

<br>

</body>

</html> 

创建update.html

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>系统首页-学生管理</title>

</head>

<body>

<h1 align="center">系统首页-学生管理-更新</h1>

<a href="/index/toIndex">返回</a>

<br>

</body>

</html> 

创建delete.html

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>系统首页-学生管理</title>

</head>

<body>

<h1 align="center">系统首页-学生管理-删除</h1>

<a href="/index/toIndex">返回</a>

<br>

</body>

</html> 

15.12 创建403页面

在static/error下面创建403.html

|

<meta charset="UTF-8">

<title>403</title>

403:你没有权限访问此页面

去首页

15.13 启动测试

注意:如果出现404问题,一般不出现这个问题

spring-boot-starter-parent org.springframework.boot 2.6.13

15.14 当用户没有某权限时,页面不展示该按钮(简单看下即可)

上一讲里面我们创建的项目里面是当用户点击页面上的链接请求到后台之后没有权限会跳转到403,那么如果用户没有权限,对应的按钮就不显示出来,这样岂不是更好吗?
我们接着上一个项目来改造
引入下面的依赖

<dependency>

    <groupId>org.thymeleaf.extras</groupId>

    <artifactId>thymeleaf-extras-springsecurity5</artifactId>

</dependency> 

修改main.html即可

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org"

      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head>

    <meta charset="UTF-8">

    <title>系统首页</title>

</head>

<body>

<h1 align="center">系统首页</h1>

<a href="/student/query" sec:authorize="hasAuthority('student:query')" >查询用户</a>

<br>

<a href="/student/add" sec:authorize="hasAuthority('student:save')" >添加用户</a>

<br>

<a href="/student/update" sec:authorize="hasAuthority('student:update')" >更新用户</a>

<br>

<a href="/student/delete" sec:authorize="hasAuthority('student:delete')" >删除用户</a>

<br>

<a href="/student/export" sec:authorize="hasAuthority('student:export')" >导出用户</a>

<br>

<br><br><br>

<h2><a href="/logout">退出</a></h2>

<br>

</body>

</html> 

重新启动登录后查看效果

16 springsecurity 集成图片验证码

以前因为我们自己写登陆的方法可以在自己的登陆方法里面去接收页面传过来的code,再和session里面正确的code进行比较 。

16.1 概述

上一讲里面我们集成了thymeleaf实现在页面链接的动态判断是否显示,那么在实际开发中,我们会遇到有验证码的功能,那么如何处理呢?
复制上一个工程springsecurity-13-thymeleaf ,修改名字为springsecurity-14-captcha

16.2 原理、存在问题、解决思路

Springsecurity的过滤器链

我们知道Spring Security是通过过滤器链来完成了,所以它的解决方案是创建一个过滤器放到Security的过滤器链中,在自定义的过滤器中比较验证码

16.3 添加依赖(用于生成验证码)

|

<groupId>cn.hutool</groupId>

<artifactId>hutool-all</artifactId>

<version>5.3.9</version>

16.4 添加一个获取验证码的接口

| @Controller

@Slf4j

public class CaptchaController {

@GetMapping("/code/image")

public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {

    //创建一个验证码
    CircleCaptcha circleCaptcha = CaptchaUtil._createCircleCaptcha_(200, 100, 2, 20);

    //放到session中
    // 为什么要重构?重构的快捷键是啥?
    String captchaCode=circleCaptcha.getCode();

    _log_.info("生成的验证码为:{}",captchaCode);

    request.getSession().setAttribute("LOGIN_CAPTCHA_CODE",captchaCode);

    ImageIO._write_(circleCaptcha.getImage(),"JPEG",response.getOutputStream());

}
}

16.5 创建验证码过滤器

| @Component

@Slf4j

public class ValidateCodeFilter extends OncePerRequestFilter {

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        validateCode(request, response,filterChain);
}

 // 验证码校验
private void validateCode(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws IOException, ServletException {

    String enterCaptchaCode = request.getParameter("code");

    HttpSession session = request.getSession();

    String captchaCodeInSession = (String) session.getAttribute("LOGIN_CAPTCHA_CODE");

    _log_.info("用户输入的验证码为:{},session中的验证码为:{}",enterCaptchaCode,captchaCodeInSession);

    //移除错误信息
    session.removeAttribute("captchaCodeErrorMsg");

    if (!StringUtils._hasText_(captchaCodeInSession)) {

        session.removeAttribute("LOGIN_CAPTCHA_CODE");

    }

    if (!StringUtils._hasText_(enterCaptchaCode) &#124;&#124; !StringUtils._hasText_(captchaCodeInSession) &#124;&#124; !enterCaptchaCode.equalsIgnoreCase(captchaCodeInSession)) {

        //说明验证码不正确,返回登陆页面
        session.setAttribute("captchaCodeErrorMsg", "验证码不正确");//重定向
        response.sendRedirect("/login/toLogin");

    }else{

        filterChain.doFilter(request,response);

    }

}    @Override    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {        //如果不是登陆请求,直接放行,不走过滤器        return !request.getRequestURI().equals("/login/doLogin");    }
}

16.6 修改WebSecurityConfig(重点)

| @EnableGlobalMethodSecurity(prePostEnabled = true)

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource

private ValidateCodeFilter validateCodeFilter;

@Override
_/**

 * Security的http请求配置
 *

 * **@param **http

 * **@throws **Exception

 */

_@Override

protected void configure(HttpSecurity http) throws Exception {
    //设置登陆方式
    http.formLogin()//使用用户名和密码的登陆方式
            .usernameParameter("uname") //页面表单的用户名的name

            .passwordParameter("pwd")//页面表单的密码的name

            .loginPage("/login/toLogin") //自己定义登陆页面的地址
            .loginProcessingUrl("/login/doLogin")//配置登陆的url

            .successForwardUrl("/index/toIndex") //登陆成功跳转的页面
            .failureForwardUrl("/login/toLogin")//登陆失败跳转的页面
            .permitAll(); // 这个不要忘了
    //配置退出方式
    http.logout()

            .logoutUrl("/logout")

            .logoutSuccessUrl("/login/toLogin")

            .permitAll();

    //配置路径拦截 的url的匹配规则 ,放行请求获取验证码的路径
 http.authorizeRequests().antMatchers("/code/image").permitAll()

            //任何路径要求必须认证之后才能访问
            .anyRequest().authenticated();

    // 禁用csrf跨站请求,注意不要写错了,因为前端页面没有传token,所以要禁用
    http.csrf().disable();

// 配置登录之前添加一个验证码的过滤器 http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class);

}_
_@Bean

public PasswordEncoder passwordEncoder() {

    return new BCryptPasswordEncoder();

}
}

16.7 修改login.html

添加验证码表单元素和图片

|

<meta charset="UTF-8">

<title>用户登陆</title>

登录页面

<table>

    <tr>

        <td>用户名:</td>

        <td><input type="text" name="uname" value="zhangsan"></td>

    </tr>

    <tr>

        <td>密码:</td>

        <td><input type="password" name="pwd"></td>

    </tr>

    <tr>

        <td>验证码:</td>

        <td><input type="text" name="code"> <img src="/code/image" style="height:33px;cursor:pointer;" onclick="this.src=this.src">

            <span th:text="${session.captchaCodeErrorMsg}" style="color: #FF0000;" >username</span>

        </td>

    </tr>

    <tr>

        <td colspan="2">

            <button type="submit">登录</button>

        </td>

    </tr>

</table>

16.8 测试登录

故意输入错误验证码

16.9 使用debug模式,查看一下自定义过滤器执行流程

17 Base64 和JWT学习

见《base64及jwt学习文档.doc》

18 JWT+Spring Security+redis+mysql 实现认证

18.1 新建工程

复制工程springsecurity-12-database-authorization-method,改名字为
springsecurity-16-jwt-authentication
注意这个工程已经有认证功能和基于方法授权的功能了。
下面咱们看下如何设置使用jwt进行认证登录。

18.2 添加jwt依赖

|

<groupId>com.auth0</groupId>

<artifactId>java-jwt</artifactId>

<version>3.11.0</version>

18.3 application.yml 中配置密钥

| jwt:

secretKey: mykey

18.4 jwt功能类

com.powernode.util包下创建

| @Component

@Slf4j

public class JwtUtils {

//算法密钥
@Value("${jwt.secretKey}")

private String jwtSecretKey;



_/**

 * 创建jwt

 *

 * **@param **userInfo 用户信息
 * **@param **authList 用户权限列表
 * **@return **返回jwt(JSON WEB TOKEN)
 */

_public String createToken(String userInfo, List<String> authList) {

    //创建时间
    Date currentTime = new Date();

    //过期时间,5分钟后过期
    Date expireTime = new Date(currentTime.getTime() + (1000 * 60 * 5));

    //jwt 的header信息
    Map<String, Object> headerClaims = new HashMap<>();

    headerClaims.put("type", "JWT");

    headerClaims.put("alg", "HS256");

    //创建jwt

    return JWT._create_()

            .withHeader(headerClaims) // 头部
            .withIssuedAt(currentTime) //已注册声明:签发日期,发行日期
            .withExpiresAt(expireTime) //已注册声明 过期时间
            .withIssuer("thomas")  //已注册声明,签发人
            .withClaim("userInfo", userInfo) //私有声明,可以自己定义
            .withClaim("authList", authList) //私有声明,可以自定义
            .sign(Algorithm._HMAC256_(jwtSecretKey)); // 签名,使用HS256算法签名,并使用密钥

// HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。
}

_/**

 * 验证jwt的签名,简称验签
 *

 * **@param **token 需要验签的jwt

 * **@return **验签结果
 */

_public boolean verifyToken(String token) {

    //获取验签类对象
    JWTVerifier jwtVerifier = JWT._require_(Algorithm._HMAC256_(jwtSecretKey)).build();

    try {

        //验签,如果不报错,则说明jwt是合法的,而且也没有过期
        DecodedJWT decodedJWT = jwtVerifier.verify(token);

        return true;

    } catch (JWTVerificationException e) {

        //如果报错说明jwt 为非法的,或者已过期(已过期也属于非法的)
        _log_.error("验签失败:{}", token);

        e.printStackTrace();

    }

    return false;

}



_/**

 * 获取用户id

 *

 * **@param **token jwt

 * **@return **用户id

 */

_public String getUserInfo(String token) {

    //创建jwt验签对象
    JWTVerifier jwtVerifier = JWT._require_(Algorithm._HMAC256_(jwtSecretKey)).build();

    try {

        //验签
        DecodedJWT decodedJWT = jwtVerifier.verify(token);

        //获取payload中userInfo的值,并返回
        return decodedJWT.getClaim("userInfo").asString();

    } catch (JWTVerificationException e) {

        e.printStackTrace();

    }

    return null;

}



_/**

 * 获取用户权限
 *

 * **@param **token

 * **@return

 ***/

_public List<String> getUserAuth(String token) {

    //创建jwt验签对象
    JWTVerifier jwtVerifier = JWT._require_(Algorithm._HMAC256_(jwtSecretKey)).build();

    try {

        //验签
        DecodedJWT decodedJWT = jwtVerifier.verify(token);

        //获取payload中的自定义数据authList(权限列表),并返回
        return decodedJWT.getClaim("authList").asList(String.class);

    } catch (JWTVerificationException e) {

        e.printStackTrace();

    }

    return null;

}
}

18.5 添加响应类

com.powernode.vo包中

| @Data

@AllArgsConstructor

@NoArgsConstructor

@Builder

public class HttpResult implements Serializable {

private Integer code; //响应码
private String msg; //响应消息
private Object data; //响应对象
}

18.6 修改SecurityUser类

加入一个获取SysUser的方法,后面会用到这个方法

| public SysUser getSysUser() {

return sysUser;
}

18.7 新建认证成功处理器

| _/**

  • 认证成功处理器,当用户登录成功后,会执行此处理器
    */

_@Component

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

//使用此工具类进行序列化
@Resource

private ObjectMapper objectMapper;

@Resource

private JwtUtils jwtUtils;


@Override

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

    response.setCharacterEncoding("UTF-8");

    response.setContentType("text/html;charset=utf-8");

    //从认证对象中获取认证用户信息
    SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();

    String userInfo=objectMapper.writeValueAsString(securityUser.getSysUser());

    List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) securityUser.getAuthorities();

    //_这可以改成stream流
    _List<String> authList=new ArrayList<>();

    for (SimpleGrantedAuthority authority : authorities) {

        authList.add(authority.getAuthority());

    }//使用stream流1,使用lambda表达式

List test = authorities.stream().map(

    a -> {

       return a.getAuthority();

    }

).collect(Collectors.toList());

System.out.println("test = " + test);

//使用stream流2

List test111 = authorities.stream().map(SimpleGrantedAuthority::getAuthority).collect(Collectors.toList());

System.out.println("test111 = " + test111);
// 创建jwt

    String token = jwtUtils.createToken(userInfo,authList);


    //返回给前端token

    HttpResult httpResult = HttpResult._builder_().code(200).msg("OK").data(token).build();

    PrintWriter writer = response.getWriter();

    writer.write(objectMapper.writeValueAsString(httpResult));

    writer.flush();

}
}

18.8 新建jwt过滤器,用于检查token等

com.powernode.filter包中新建类

| _/**

  • 定义一次性请求过滤器
    */

_@Component

@Slf4j

public class JwtCheckFilter extends OncePerRequestFilter {

@Resource

private ObjectMapper objectMapper;

@Resource

private JwtUtils jwtUtils;


@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    //获取请求头中的Authorization

    String authorization = request.getHeader("Authorization");

    //如果Authorization为空,那么不允许用户访问,直接返回
    if (!StringUtils._hasText_(authorization)) {

        printFront(response, "没有登录!");

        return;

    }

    //Authorization 去掉头部的Bearer 信息,获取token值
    String jwtToken = authorization.replace("Bearer ", "");

    //验证签名(简称验签)
    boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);

    //验签不成功
    if (!verifyTokenResult) {

        printFront(response, "jwtToken 已过期");

        return;

    }


    //从payload中获取userInfo

    String userInfo = jwtUtils.getUserInfo(jwtToken);

    //从payload中获取授权列表
    List<String> userAuth = jwtUtils.getUserAuth(jwtToken);

    //创建登录用户
    SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);

    SecurityUser securityUser = new SecurityUser(sysUser);

    //设置权限
    List<SimpleGrantedAuthority> authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors._toList_());

    securityUser.setAuthorityList(authList);

//创建用户名密码token
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser

            , null, authList);

    //通过安全上下文设置认证信息
    SecurityContextHolder._getContext_().setAuthentication(usernamePasswordAuthenticationToke);

    //继续访问相应的url等
    filterChain.doFilter(request, response);



}



private void printFront(HttpServletResponse response, String message) throws IOException {

    response.setCharacterEncoding("UTF-8");

    response.setContentType("application/json;charset=utf-8");

    PrintWriter writer = response.getWriter();

    HttpResult httpResult =HttpResult.builder().code(-1).msg(message).build();

    writer.print(objectMapper.writeValueAsString(httpResult));

    writer.flush();

}    @Override    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {        //如果是登陆请求,直接放行,不走过滤器        return request.getRequestURI().equals("/login");    }
}

18.9修改 web安全配置类WebSecurityConfig

| @EnableGlobalMethodSecurity(prePostEnabled = true)

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Resource

private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

@Resource

private JwtCheckFilter jwtCheckFilter;
@Override

protected void configure(HttpSecurity http) throws Exception {

    http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);

    http.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();

http.authorizeRequests()

    .mvcMatchers("/student/**").hasAnyAuthority("student:query","student:update")
            .anyRequest().authenticated(); //任何请求均需要认证(登录成功)才能访问//禁用跨域请求保护
    http.csrf().disable();

    //禁用session

    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy._STATELESS_);

}



@Bean

public PasswordEncoder passwordEncoder(){

    return new BCryptPasswordEncoder();

}
}

18.10 启动测试

先登录系统,获取页面上返回的token,然后使用postman 在请求头中携带token发送请求即可。

使用curl 访问

curl -H “Authorization:Bearer token” localhost:8080/student/query

18.11 测试后的问题

18.11.1 实现用户退出的问题

问题:因为JWT无状态,如果要实现退出功能无法实现。
解决办法:
使用redis
步骤:
① 登陆成功之后把生成JWT存到redis中

keyvalue
logintoken:jwt认证信息authentication

② 用户退出时,从redis中删除该token
③ 用户每次访问时,先校验jwt是否合法,如果合法再从redis里面取出logintoken:jwt判断这个jwt还存不存在,如果不存在就说明用户已经退出来,就返回未登陆。

18.11.2 启动redis并使用客户端工具连接到redis

18.11.3 复制工程

复制springsecurity-16-jwt-authentication 成springsecurity-16-jwt-authentication-redis工程

18.11.4 加入redis依赖

|

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

18.11.5 配置redis信息

| spring:

** ****redis:

host: 192.168.43.33

port: 6379

database: 0

password: 666666** |

| — |

18.11.6 修改认证成功处理器

添加依赖注入

| @Resource

private StringRedisTemplate stringRedisTemplate;

代码中加入

stringRedisTemplate.opsForValue().set(“logintoken:”+token,objectMapper.writeValueAsString(authentication),30, TimeUnit.MINUTES);

18.11.7 新建用户退出成功处理器

| _/**

  • 退出成功处理器,用户退出成功后,执行此处理器
    */

_@Component

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

//使用此工具类的对象进行序列化操作
@Resource

private ObjectMapper objectMapper;

@Resource

private StringRedisTemplate stringRedisTemplate;

@Override

public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

    //从请求头中获取Authorization信息
    String authorization = request.getHeader("Authorization");

    //如果授权信息为空,返回前端
    if(null==authorization){

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        HttpResult httpResult=HttpResult._builder_().code(-1).msg("token不能为空").build();

        PrintWriter writer = response.getWriter();

        writer.write(objectMapper.writeValueAsString(httpResult));

        writer.flush();

        return;

    }

    //如果Authorization信息不为空,去掉头部的Bearer字符串
    String token = authorization.replace("Bearer ", "");

    //redis中删除token,这是关键点
    stringRedisTemplate.delete("logintoken:"+token);

    response.setCharacterEncoding("UTF-8");

    response.setContentType("application/json;charset=utf-8");

    HttpResult httpResult=HttpResult._builder_().code(200).msg("退出成功").build();

    PrintWriter writer = response.getWriter();

    writer.write(objectMapper.writeValueAsString(httpResult));

    writer.flush();

}
}

配置用户成功退出处理器
修改WebSecurityConfig
依赖注入

| @Resource

private MyLogoutSuccessHandler myLogoutSuccessHandler;

添加代码

http.logout().logoutSuccessHandler(myLogoutSuccessHandler);http.csrf().disable(); //注意:禁用跨域请求保护 要不然logout不能访问

18.11.8 修改jwtcheckfilter

添加依赖注入

| @Resource

private StringRedisTemplate stringRedisTemplate;

代码中加入

| // 从redis中获取token

    String tokenInRedis = stringRedisTemplate.opsForValue().get("logintoken:" + jwtToken);

    if(!StringUtils._hasText_(tokenInRedis)){

        printFront(response, "用户已退出,请重新登录");

        return;

    } |

| — |

18.11.9 启动程序并登录测试

登录后查看redis中是否存储了token

18.11.10 使用jwt token访问/student/query

使用postman测试,发现可以正常访问

18.11.11 使用postman 退出系统


注意携带token,才能退出啊
注意:要禁用跨域请求保护,要不然使用postman无法访问logout端点

http.csrf().disable(); //禁用跨域请求保护

18.11.12 再次使用token访问/student/query

现象:发现已经不能正常访问了
原因:虽然token本身并没有过期,但是redis中已经删除了该token,所以不能正常访问了
使用curl 访问

curl -H “Authorization:Bearer token” localhost:8080/student/query

18.11.13 使用debug模式,理解整个流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/451030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

西门子s7-300/400PLC-MMC密码解密

西门子s7-300/400-MMC密码解密 简介西门子加密工具及操作密码验证 简介 目前&#xff0c;市面上或网络上有很多针对s7-200&#xff0c;300&#xff0c;400&#xff0c;1200&#xff0c;1500的密码解密破解软件&#xff0c;但很多时候只能解数字或英文密码&#xff0c;对设置了…

Linux-初学者系列——篇幅5_系统目录相关命令

系统目录相关命令-目录 一、系统目录层级1、目录绝对路径2、目录相对路径3、目录层级结构查看-tree不带任何参数获取目录结构数据信息以树形结构显示目录下的所有内容&#xff08;包含隐藏信息&#xff09;只列出根目录下第一层的目录结构信息只显示目录结构信息中的所有目录信…

ThingsBoard如何自定义topic

1、背景 业务需要,mqtt设备,他们协议和topic都定义好了,想使用tb的mqtt直接接入设备,但是设备的topic和tb规定的不一致,该如何解决呢? 2、要求 设备的topic要求规则是这样的 首先第二点是满足的,网关的发布主题是可以通过tb的设备配置来自定义遥测和属性的topic,问题…

qiankun应用级缓存-多页签缓存

需求&#xff1a; A&#xff1a;主应用 B&#xff1a;子应用 项目框架&#xff1a;vue2 全家桶 qiankun 应用间切换需要保存页面缓存&#xff08;多页签缓存&#xff09;&#xff0c;通过vue keep-alive只能实现页面级缓存&#xff0c;在单独打开的应用里能实现缓存&#xf…

德国申请专利,发明,实用,外观专利申请详细步骤

一、德国专利申请途径 申请人可以向德国专利商标局直接递交申请。要求优先权的情况下根据《保护工业产权巴黎公约》需要在递交中国在先申请的12个月之内向德国专利商标局递交申请。 另外&#xff0c;通过PCT&#xff08;“Patent Cooperation Treaty”&#xff0c;即《专利合作…

【分享】免费的AI绘画网站(5个)

哈喽&#xff0c;大家好&#xff0c;我是木易巷~ 随着人工智能技术的不断发展&#xff0c;越来越多的AI绘画软件开始涌现&#xff0c;如果你想要免费享受AI绘画的乐趣&#xff0c;那你可要好好看下面的内容~ Vega AI创作平台 入口&#xff1a;https://rightbrain.art 一款专业的…

AIGC+RPA丨大语言模型赋能实在智能数字员工“超进化”

前不久&#xff0c;全球最大上市咨询公司埃森哲发布2023年技术愿景《When Atoms meet Bits》报告&#xff0c;并在当中深度解析到&#xff1a;生成式AI成为2023年四大技术发展趋势之一。 大型语言模型&#xff08;Large Language Model, LLM&#xff09;领域的研发和布局在国内…

如何科学判断研发团队是否在健康工作?(内附量表)

研发效能管理覆盖了交付速度、质量和价值三个维度&#xff0c;但文化建设、团队氛围和客户协作等其他因素对团队工作的影响又该如何度量和管理呢&#xff1f; LigaAI 在 John Cutler 的一篇分享中找到了答案&#xff1a;团队健康度评分。就像我们都很关心自己的身体健康一样&a…

《程序员面试金典(第6版)》面试题 16.02. 单词频率(哈希法,C++)

题目描述 设计一个方法&#xff0c;找出任意指定单词在一本书中的出现频率。 你的实现应该支持如下操作&#xff1a; WordsFrequency(book)构造函数&#xff0c;参数为字符串数组构成的一本书get(word)查询指定单词在书中出现的频率 示例&#xff1a; WordsFrequency word…

DevEco Studio 3.1 Beta新特性知多少

HUAWEI DevEco Studio是开发HarmonyOS应用及服务的一站式集成开发环境&#xff08;IDE&#xff09;。本次&#xff0c;DevEco Studio 3.1 Beta新增支持当前在市面上新的操作系统&#xff0c;如Windows11、macOS&#xff08;ARM&#xff09;操作系统&#xff0c;还面向HarmonyOS…

4.数据结构(0x3f:从周赛中学算法 2022下)

来自0x3f【从周赛中学算法 - 2022 年周赛题目总结&#xff08;下篇&#xff09;】&#xff1a;https://leetcode.cn/circle/discuss/WR1MJP/ 包括堆&#xff08;优先队列&#xff09;、单调栈、单调队列、字典树、并查集、树状数组、线段树等。 学习这些只是开始&#xff0c;能…

Pytorch对机器学习模型的安全漏洞攻击方法之Fast Gradient Sign Attack(FGSM,快速梯度符号攻击)

原论文&#xff1a;EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES 一般本人的习惯是先看论文熟悉它&#xff0c;然后代码去实现它&#xff0c;这样感觉要好点。因为论文讲解的比较全面和一些实验对比还有很多的引用等&#xff0c;另外大家知道好论文基本都是英文&#xff0c…

【Python】【进阶篇】2、Django MTV和MVC的区别

目录 2、Django MTV和MVC的区别1. MVC设计模式2. MTV设计模式 2、Django MTV和MVC的区别 在上一节《Django是什么》中&#xff0c;我们对 Django 的诞生以及 Web 框架的概念有了基本的了解&#xff0c;本节我们介绍 Django 的设计模式&#xff0c;也就是 MTV。 在 Web 开发领…

Point cloud tools for Matlab(点云学习工具)

Point cloud tools for Matlab (tuwien.ac.at)https://www.geo.tuwien.ac.at/downloads/pg/pctools/pctools.html#PointCloud_class 下载&#xff1a;Download Matlab Code 添加路径 addpath(genpath(D:\MyMatlabCode\pointCloudTools)); pc pointCloud(Lion.xyz); pc.plot…

AI绘画——ChilloutMix模型(现实真人,实现写实逼真的图像)

目录 重要提示&#xff1a;首先&#xff0c;我从不建议再生“真实”人物的图像&#xff0c; 但是&#xff0c;照片“逼真”图像。 本人郑重声明&#xff1a;本模型原则上禁止用于训练基于明星、公众人物肖像的风格模型训练&#xff0c;因为这会带来争议&#xff0c;对AI社区…

全网详细介绍百度低代码框架amis及实例讲解

文章目录 1. 文章引言2. amis官网3. amis介绍3.1 amis的简述3.2 amis的初衷3.3 amis的其他亮点3.4 amis不适之处 4. amis使用4.1 amis框架集成4.2 amis切换主题4.3 amis配置与组件4.4 amis变量 5. amis实例5.1 amis实例5.2 列表查询5.3 查看详情5.4 自定义操作按钮5.5 提交表单…

你知道ChatGPT中的数据模型是什么吗?

本文将为你揭开ChatGPT和GPT-4中使用的以数据为中心的人工智能技术的秘密。 人工智能在改变我们的生活、工作和与技术互动的方式方面取得了令人难以置信的进步。最近&#xff0c;一个取得重大进展的领域是大型语言模型&#xff08;LLM&#xff09;的开发&#xff0c;如​​GPT…

二十、SQL 数据分析基础与进阶(一)

文章目录 一、破冰 SELECT 基础检索1.1 检索所需要的列1.1.1 检索单列数据1.1.2 检索多列数据 1.2 * 符号初体验1.3 独特的 DISTINCT1.4 使用 ORDER BY 排序检索结果1.5 使用 LIMIT 限制返回行数1.6 ORDER BY 与 LIMIT 结合的妙用 二、过滤数据&#xff0c;选你所想三、计算字段…

ArcGIS Pro快捷键

目录 1 常规应用快捷键 2 动画快捷键 3 内容窗格快捷键 4 数据工程试图快捷键 5 编辑工具快捷键 5.1 常规编辑 5.3 选择工具 5.4 表 5.5 文本格式化标签 5.6 编辑注记 5.7 移动 5.8 旋转 5.9 比例 5.10 编辑折点 5.11 几何属性表 5.12 创建注记 5.13 创建点要…

安装 FME Desktop 2020 教程(内置补丁可以有效激活软件)

介绍&#xff1a;FME Desktop 2020是由加拿大Safe Software公司开发的空间数据转换处理系统&#xff0c;采用先进的数据转换技术&#xff0c;内置几十个转换器&#xff0c;能够满足绝大部分用户的空间数据格式转换功能。除此之外&#xff0c;该软件基于OpenGIS组织提出的新的数…