[Power node] Chapter 14-18 of Springsecurity JWT+Spring Security+redis+mysql to achieve authentication

15 Spring Security integrated thymeleaf

This project is based on springsecurity-12-database-authorization-method Copy
springsecurity-12-database-authorization-method and renamed to springsecurity-13-thymeleaf

15.1 Add thymeleaf dependency

|

org.springframework.boot

spring-boot-starter-thymeleaf

15.2 Modify application.yml

Add thymeleaf configuration

| spring:

thymeleaf:

cache: false # Do not use cache check-template: true # Check if the thymeleaf template exists

15.3 idea add thymeleaf template

【File】—》【Settings...】

The template name is thymeleaf, the extension name is html, and the specific content is as follows:

|

#[[$Title$]]#       

#[[ END ENDEND]]#

A brief description:

#[[ T i t l e Title Title ]]# # [ [ END ENDEND ]]# The function of these two places is that when you create a new template page, in标签中输入标题内容后,只需要点击回车键,光标就会直接跳到内,省去了你挪动鼠标,或者挪动方向键的步骤,也可以给你节省一点点时间。<strong>新版****idea可能有点问题,实验一下Enable Live Template</strong>

15.4 New LoginController

| @Controller

@RequestMapping(“/login”)

public class LoginController {

_/**

* Jump to login page
     */

_@RequestMapping(“/toLogin”)

public String toLogin(){

return “login”;

}

}

15.5 Create thymeleaf file login.html

Create login.html under templates, using the template to create

|

用户登陆

log in page

username:

password:

Log in

15.7 Modify the security configuration file WebSecurityConfig

The modification is as follows:

| @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 {

//Set the login method
        http.formLogin()//Login method using username and password.usernameParameter
                ("uname") //The name of the user name of the page form

.passwordParameter("pwd")//The name of the password of the page form

.loginPage(“/login/toLogin”) //Define the address of the login page
                yourself.loginProcessingUrl(“/login/doLogin”)//Configure the login url

.successForwardUrl(“/index/toIndex”) //The page that jumps after successful login.failureForwardUrl
                (“/login/toLogin”)//The page that jumps after login failure.permitAll
                (); //Release the url related to login, Don't forget to write this
        //Configure the exit method
        http.logout()

.logoutUrl(“/logout”)

.logoutSuccessUrl(“/login/toLogin”)

.permitAll();/Release and exit related urls, don’t forget to write this

//Configure the matching rules of the url intercepted by the path
        http.authorizeRequests()

//Any path requires authentication before accessing.anyRequest
                ().authenticated();

// Disable csrf cross-site request attack You can use the postman tool to test later, pay attention to disable csrf
        http.csrf().disable();

}

}

15.8 Create IndexController

| @Controller

@RequestMapping(“/index”)

public class IndexController {

_/**

* Enter the homepage after successful login
     */

_@RequestMapping(“/toIndex”)

public String toIndex(){

return “main”;

}

}

15.9 Create thymeleaf file main.html

Create main.html under templates

|

系统首页

System Home

Query students


add students


update students


delete student


export students





quit


15.10 Modifying the Studentcontroller

The modification is as follows:

| @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 Create each page of student management under templates/user

Create export.html

|

系统首页-学生管理

System Home-Student Management-Export

return


Create query.html

|

系统首页-学生管理

System Home-Student Management-Query

return


Create add.html

|

系统首页-学生管理

System home page - student management - add

return


Create update.html

|

系统首页-学生管理

System Home-Student Management-Update

return


Create delete.html

|

系统首页-学生管理

System Home-Student Management-Delete

return


15.12 Create 403 page

Create 403.html under static/error

|

403

403: You do not have permission to access this page

go to home page

15.13 Start Test

Note: If there is a 404 problem, generally this problem does not occur

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

15.14 When the user does not have a certain permission, the page does not display the button (just look at it)

In the project we created in the last lecture, when the user clicks the link on the page to request to go to the background, it will jump to 403 without permission. If the user does not have permission, the corresponding button will not be displayed. Wouldn’t it be better?
Let's continue with the previous project to transform
and introduce the following dependencies

|

org.thymeleaf.extras

thymeleaf-extras-springsecurity5

Just modify main.html

|

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

系统首页

System Home

Query users


Add user


update user


delete users


export users





quit


Check the effect after restarting and logging in

16 springsecurity integrated image verification code

In the past, because we wrote our own login method, we could receive the code passed from the page in our own login method, and then compare it with the correct code in the session.

16.1 Overview

In the previous lecture, we integrated thymeleaf to realize the dynamic judgment of whether to display the page link. Then in the actual development, we will encounter the function of verification code, so how to deal with it?
Copy the previous project springsecurity-13-thymeleaf, modify the name to springsecurity-14-captcha

16.2 Principles, Existing Problems, and Solutions

Spring Security's filter chain

We know that Spring Security is done through the filter chain, so its solution is to create a filter and put it in the Security filter chain, and compare the verification code in the custom filter

16.3 Add dependencies (for generating verification codes)

|

cn.hutool

hutool-all

5.3.9

16.4 Add an interface to get the verification code

| @Controller

@Slf4j

public class CaptchaController {

@GetMapping(“/code/image”)

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

//Create a captcha
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha ( 200, 100, 2, 20);

//Put it in the session
        //Why do you want to refactor? What is the shortcut key for refactoring?
        String captchaCode = circleCaptcha. getCode();

log .info("The generated verification code is: {}", captchaCode);

request.getSession().setAttribute(“LOGIN_CAPTCHA_CODE”,captchaCode);

ImageIO.write(circleCaptcha.getImage(),“JPEG”,response.getOutputStream());

}

}

16.5 Create captcha filter

| @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("The verification code entered by the user is: {}, the verification code in the session is: {}", enterCaptchaCode, captchaCodeInSession);

//Remove the error message
        session.removeAttribute("captchaCodeErrorMsg");

if (!StringUtils.hasText(captchaCodeInSession)) {

session.removeAttribute(“LOGIN_CAPTCHA_CODE”);

}

if (!StringUtils.hasText(enterCaptchaCode) || !StringUtils.hasText(captchaCodeInSession) || !enterCaptchaCode.equalsIgnoreCase(captchaCodeInSession)) {

//Indicates that the verification code is incorrect, return to the login page
            session.setAttribute(“captchaCodeErrorMsg”, “The verification code is incorrect”);//Redirect
            response.sendRedirect(“/login/toLogin”);

}else{

filterChain.doFilter(request,response);

}

} @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { //If it is not a login request, let it pass without going through the filter return !request.getRequestURI().equals(“/login/doLogin”); }

}

16.6 Modify WebSecurityConfig (emphasis)

| @EnableGlobalMethodSecurity(prePostEnabled = true)

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource

private ValidateCodeFilter validateCodeFilter;

@Override
    _/**

* Security http request configuration
     *

* **@param **http

* **@throws **Exception

*/

_@Override

protected void configure(HttpSecurity http) throws Exception {         //Set the login method         http.formLogin()//Login method using username and password.usernameParameter                 ("uname") //The name of the user name of the page form


.passwordParameter("pwd")//The name of the password of the page form

.loginPage(“/login/toLogin”) //Define the address of the login page
                yourself.loginProcessingUrl(“/login/doLogin”)//Configure the login url

.successForwardUrl(“/index/toIndex”) //The page that jumps after successful login
                .failureForwardUrl(“/login/toLogin”)//The page that jumps after login
                failure.permitAll(); // Don't forget this
        //Configuration Exit method
        http.logout()

.logoutUrl(“/logout”)

.logoutSuccessUrl(“/login/toLogin”)

.permitAll();

//Configure the matching rules of the url intercepted by the path, and release the request to obtain the verification code path
     http.authorizeRequests().antMatchers(“/code/image”).permitAll()

//Any path requires authentication before accessing.anyRequest
                ().authenticated();

// Disable csrf cross-site request, be careful not to write it wrong, because the front-end page does not pass token, so
        http.csrf().disable() should be disabled;

// Configure a filter to add a verification code before logging in       http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class);

}_
    _@Bean

public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

}

16.7 Modify login.html

Add captcha form elements and images

|

用户登陆

log in page

username:

password:

verification code:

username

Log in

16.8 Test login

Intentionally entered wrong verification code

16.9 Use the debug mode to check the custom filter execution process

17 Base64 and JWT learning

See "base64 and jwt learning document.doc"

18 JWT+Spring Security+redis+mysql to achieve authentication

18.1 New construction

Copy the project springsecurity-12-database-authorization-method, and change the name to
springsecurity-16-jwt-authentication
Note that this project already has authentication functions and method-based authorization functions.
Let's take a look at how to set up authentication and login using jwt.

18.2 Add jwt dependency

|

com.auth0

java-jwt

3.11.0

18.3 Configure keys in application.yml

| jwt:

secretKey: mykey

18.4 jwt function class

Created under com.powernode.util package

| @Component

@Slf4j

public class JwtUtils {

//Algorithm key
    @Value("${jwt.secretKey}")

private String jwtSecretKey;

_/**

* create jwt

*

* **@param **userInfo user information
     * **@param **authList user permission list
     * **@return **return jwt (JSON WEB TOKEN)
     */

_public String createToken(String userInfo, List authList) {

// Create time
        Date currentTime = new Date();

//Expiration time, expires in 5 minutes
        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”);

//Create jwt

return JWT.create()

.withHeader(headerClaims) // head.withIssuedAt
                (currentTime) //registered claim: issue date, issue date.withExpiresAt
                (expireTime) //registered claim expiration time.withIssuer
                ("thomas") //registered claim, Issuer.withClaim
                (“userInfo”, userInfo) //Private claim, you can define it yourself.withClaim
                (“authList”, authList) //Private claim, you can customize
                it.sign(Algorithm.HMAC256 ( jwtSecretKey )); // Signature , signed using the HS256 algorithm, and using a key
// HS256 is a symmetric algorithm, which means there is only one key, shared between the two parties. Use the same key to generate a signature and verify it. Special attention should be paid to whether the key is kept confidential.
    }

_/**

* Verify the signature of jwt, referred to as signature verification
     *

* **@param **token jwt that needs to be verified

* **@return **signature verification result
     */

_public boolean verifyToken(String token) {

//Get the signature verification class object
        JWTVerifier jwtVerifier = JWT.require (Algorithm.HMAC256 ( jwtSecretKey )).build();

try {

//Verify the signature, if no error is reported, it means that the jwt is legal and has not expired
            DecodedJWT decodedJWT = jwtVerifier.verify(token);

return true;

} catch (JWTVerificationException e) {

//If an error is reported, it means that jwt is illegal, or has expired (expired is also illegal)
            log .error("signature verification failed: {}", token);

e.printStackTrace();

}

return false;

}

_/**

* Get user id

*

* **@param **token jwt

* **@return **User ID

*/

_public String getUserInfo(String token) {

// Create jwt signature verification object
        JWTVerifier jwtVerifier = JWT.require (Algorithm.HMAC256 ( jwtSecretKey )).build();

try {

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

//Get the value of userInfo in the payload, and return
            return decodedJWT.getClaim(“userInfo”).asString();

} catch (JWTVerificationException e) {

e.printStackTrace();

}

return null;

}

_/**

* Get User Permissions
     *

* **@param **token

* **@return

***/

_public List getUserAuth(String token) {

// Create jwt signature verification object
        JWTVerifier jwtVerifier = JWT.require (Algorithm.HMAC256 ( jwtSecretKey )).build();

try {

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

//Get the custom data authList (permission list) in the payload, and return
            return decodedJWT.getClaim(“authList”).asList(String.class);

} catch (JWTVerificationException e) {

e.printStackTrace();

}

return null;

}

}

18.5 Adding a Response Class

com.powernode.vo package

| @Data

@AllArgsConstructor

@NoArgsConstructor

@Builder

public class HttpResult implements Serializable {

private Integer code; //response code
    private String msg; //response message
    private Object data; //response object

}

18.6 Modifying the SecurityUser class

Add a method to get SysUser, which will be used later

| public SysUser getSysUser() {

return sysUser;

}

18.7 Create a new authentication success handler

| _/**

* Authentication success processor, when the user logs in successfully, this processor will be executed
 */

_@Component

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

//Use this tool class for serialization
    @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”);

//Get the authentication user information from the authentication object
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();

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

List authorities = (List) securityUser.getAuthorities();

//_This can be changed to stream_List
        authList=new ArrayList<>();

for (SimpleGrantedAuthority authority : authorities) {

authList.add(authority.getAuthority());

}//Use stream 1, use lambda expression
List test = authorities.stream().map(

a -> {

return a.getAuthority();

}

).collect(Collectors.toList());

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

//Use stream 2

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

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

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

//Return to the front-end 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 Create a new jwt filter for checking tokens, etc.

New class in com.powernode.filter package

| _/**

* Define a one-time request 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 {

//Get the Authorization in the request header

String authorization = request.getHeader(“Authorization”);

//If Authorization is empty, then the user is not allowed to access, return directly
        if (!StringUtils.hasText (authorization)) {

printFront(response, "Not logged in!");

return;

}

//Authorization Remove the Bearer information in the header and get the token value
        String jwtToken = authorization.replace("Bearer ", "");

//Verify signature (signature verification for short)
        boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);

//signature verification is unsuccessful
        if (!verifyTokenResult) {

printFront(response, "jwtToken has expired");

return;

}

//Get userInfo from payload

String userInfo = jwtUtils.getUserInfo(jwtToken);

//Get the authorization list from the payload
        List userAuth = jwtUtils.getUserAuth(jwtToken);

//Create login user
        SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);

SecurityUser securityUser = new SecurityUser(sysUser);

//Set authority
        List authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList ( ));

securityUser.setAuthorityList(authList);

// Create username password token
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser

, null, authList);

// Set authentication information through security context
        SecurityContextHolder.getContext ().setAuthentication(usernamePasswordAuthenticationToke);

//Continue to visit the corresponding url, etc.
        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 { //If it is a login request, let it pass without going through the filter return request.getRequestURI().equals(“/login”); }

}

18.9 Modify the web security configuration class 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(); //Any request requires authentication (successful login) to access// Disable cross-origin request protection
        http.csrf().disable();

// disable session

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

}

@Bean

public PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

}

18.10 Start Test

Log in to the system first, get the token returned on the page, and then use postman to send the request with the token in the request header.

Use curl to access

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

18.11 Post-Test Questions

18.11.1 Issues with Implementing User Logout

Problem: Because JWT is stateless, it cannot be implemented if the exit function is to be implemented.
Solution:
Use redis
steps:
① After successful login, save the generated JWT to redis

key value
logintoken:jwt Authentication information authentication

② When the user logs out, delete the token from redis.
③ Every time the user visits, first check whether the jwt is legal. If it is legal, then take out the logintoken from redis:jwt judges whether the jwt still exists. If it does not exist, it means that the user has If you log out, you will return to not logged in.

18.11.2 Start redis and use client tools to connect to redis

18.11.3 Copy project

Copy springsecurity-16-jwt-authentication into springsecurity-16-jwt-authentication-redis project

18.11.4 Add redis dependency

|

org.springframework.boot

spring-boot-starter-data-redis

18.11.5 Configure redis information

| spring:

** ****redis:

host: 192.168.43.33

port: 6379

database: 0

password: 666666**

18.11.6 Modify authentication success handler

Add dependency injection

| @Resource

private StringRedisTemplate stringRedisTemplate;

add in code

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

18.11.7 New user logout success handler

| _/**

* Exit success handler, after the user exits successfully, execute this handler
 */

_@Component

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

//Use the object of this tool class for serialization
    @Resource

private ObjectMapper objectMapper;

@Resource

private StringRedisTemplate stringRedisTemplate;

@Override

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

// Get Authorization information from the request header
        String authorization = request.getHeader("Authorization");

//If the authorization information is empty, return to the front end
        if(null==authorization){

response.setCharacterEncoding(“UTF-8”);

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

HttpResult httpResult=HttpResult.builder ().code(-1).msg("token cannot be empty").build() ;

PrintWriter writer = response.getWriter();

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

writer.flush();

return;

}

//If the Authorization information is not empty, remove the Bearer string in the header
        String token = authorization.replace("Bearer ", "");

//Delete token in redis, this is the key point
        stringRedisTemplate.delete("logintoken:"+token);

response.setCharacterEncoding(“UTF-8”);

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

HttpResult httpResult=HttpResult.builder ().code(200).msg("Exit successful").build() ;

PrintWriter writer = response.getWriter();

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

writer.flush();

}

}

Configure the user to successfully exit the processor
Modify WebSecurityConfig
dependency injection

| @Resource

private MyLogoutSuccessHandler myLogoutSuccessHandler;

add code

http.logout().logoutSuccessHandler(myLogoutSuccessHandler);http.csrf().disable(); //Note: Disable cross-domain request protection or logout cannot be accessed

18.11.8 Modify jwtcheckfilter

Add dependency injection

| @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模式,理解整个流程

Guess you like

Origin blog.csdn.net/f5465245/article/details/130317120