《Spring Security+JWT实现认证授权》

前面,我们做了Spring Security和JWT的详细介绍,今天,我们就来整合Spring Security 和 JWT……
《Spring Security十万字详解》
《JWT 三万字详解》

话不多说,上代码

安全配置类

package com.uncle.jwt.config;

import com.uncle.jwt.filter.TokenAuthenticationFilter;
import com.uncle.jwt.filter.TokenLoginFilter;
import com.uncle.jwt.handler.UnauthorizedEntryPoint;
import com.uncle.jwt.util.DefaultPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 14:24
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled=true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 密码管理工具类
     */
    @Autowired
    private DefaultPasswordEncoder defaultPasswordEncoder;

    /**
     * 用户服务类
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 配置设置,设置退出的地址和token
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                //未授权处理
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().authorizeRequests()
                .anyRequest().authenticated()
                .and().csrf().disable()
                .logout().logoutUrl("/logout")
                .and()
                .addFilter(new TokenLoginFilter(authenticationManager()))
                .addFilter(new TokenAuthenticationFilter(authenticationManager())).httpBasic();
    }

    /**
     * 密码处理
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**");
    }
}


测试接口

package com.uncle.jwt.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 14:25
 */
@RestController
public class TestController {

    @GetMapping("admin")
    public String admin() {
        return "admin!!!";
    }

    @GetMapping("api/hello")
    public String user() {
        return "hello world";
    }


    /**
     * 方法执行前鉴权
     *
     * @return
     */
    @GetMapping("roleAdmin")
    @Secured("ROLE_ADMIN")
    public String roleAdmin() {
        return "roleAdmin!!!";
    }

    /**
     * 方法执行前鉴权
     *
     * @return
     */
    @GetMapping("preAuthorize")
    @PostAuthorize("hasAnyRole('ROLE_ADMIN')")
    public String preAuthorize() {
        System.out.println("preAuthorize…………方法执行前鉴权");
        return "preAuthorize!!!";
    }

    /**
     * 方法执行完再鉴权
     *
     * @return
     */
    @GetMapping("postAuthorize")
    @PostAuthorize("hasAnyRole('ROLE_USER')")
    public String postAuthorize() {
        System.out.println("postAuthorize…………方法执行完再鉴权");
        return "PostAuthorize!!!";
    }
}

登录过滤器

package com.uncle.jwt.filter;

import com.uncle.jwt.util.JwtTokenUtil;
import com.uncle.jwt.util.ResponseUtil;
import com.uncle.jwt.util.Result;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 14:38
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;

    public TokenLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
        try {
            String username = req.getParameter("username");
            String password = req.getParameter("password");
            username = username != null ? username.trim() : "";
            password = password != null ? password : "";
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 登录成功
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException {
        User user = (User) auth.getPrincipal();
        String authrorities = user.getAuthorities().size() > 0 ? user.getAuthorities().toString().replaceAll("(?:\\[|null|\\]| +)", "") : user.getAuthorities().toString();

        String token = JwtTokenUtil.createToken(user.getUsername(), authrorities);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("user", user);
        map.put("loginName", user.getUsername());
        ResponseUtil.out(response, Result.ok(map));
    }

    /**
     * 登录失败
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(response, Result.error("登录失败"));
    }
}


package com.uncle.jwt.filter;

import com.uncle.jwt.util.JwtTokenUtil;
import com.uncle.jwt.util.ResponseUtil;
import com.uncle.jwt.util.Result;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 14:56
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {

    public TokenAuthenticationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        logger.info("=================" + request.getRequestURI());
        //不需要鉴权
        if (request.getRequestURI().indexOf("index") != -1) {
            chain.doFilter(request, response);
        }
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(request);
        } catch (Exception e) {
            ResponseUtil.out(response, Result.error(e.getMessage()));
        }
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(response, Result.error("鉴权失败"));
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // 获取Token字符串,token 置于 header 里
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            token = request.getParameter("token");
        }
        if (token != null && !"".equals(token.trim())) {
            // 从Token中解密获取用户名
            String userName = JwtTokenUtil.getUserNameFromToken(token);

            if (userName != null) {
                // 从Token中解密获取用户角色
                String role = JwtTokenUtil.getUserRoleFromToken(token);
                // 将ROLE_XXX,ROLE_YYY格式的角色字符串转换为数组
                String[] roles = role.split(",");
                Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
                for (String s : roles) {
                    authorities.add(new SimpleGrantedAuthority(s));
                }
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}

处理异常类

package com.uncle.jwt.handler;

import com.uncle.jwt.util.ResponseUtil;
import com.uncle.jwt.util.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

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

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 15:19
 */
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, Result.error("未授权统一处理"));
    }
}

处理用户登录过程的类

package com.uncle.jwt.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.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 java.util.ArrayList;
import java.util.List;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 15:11
 */
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //从数据库获取用户信息
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        //封装用户信息,用于认证与密码校验
        return new User(username, username, authorities);
    }
}


加密处理器

package com.uncle.jwt.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 15:37
 */
@Component
@Slf4j
public class DefaultPasswordEncoder extends BCryptPasswordEncoder {

    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword cannot be null");
        }
        if (encodedPassword == null || encodedPassword.length() == 0) {
            log.error("Empty encoded password");
            throw new IllegalArgumentException("encodedPassword is null");
        }
        return encodedPassword.equals(rawPassword);
    }
}

JWT工具类

package com.uncle.jwt.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 16:34
 */
public class JwtTokenUtil {
    private static long tokenExpiration = 24 * 60 * 60 * 1000;
    private static String tokenSignKey = "123456";
    private static String userRoleKey = "userRole";

//    public String createToken(String userName) {
//        String token = Jwts.builder().setSubject(userName)
//                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
//                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
//        return token;
//    }

    public static String createToken(String userName, String role) {
        String token = Jwts.builder().setSubject(userName)
                .claim(userRoleKey, role)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }

    public static String getUserNameFromToken(String token) {
        String userName = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return userName;
    }

    public static String getUserRoleFromToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
        return claims.get(userRoleKey).toString();
    }

}

响应有关的类

package com.uncle.jwt.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 15:56
 */
public class ResponseUtil {
    public static void out(HttpServletResponse response, Result result) {
        ObjectMapper mapper = new ObjectMapper();
        PrintWriter writer = null;
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            writer = response.getWriter();
            mapper.writeValue(writer, result);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.flush();
                writer.close();
            }
        }
    }
}

package com.uncle.jwt.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 16:12
 */
@Data
public class Result implements Serializable {

    /**
     * 定义jackson对象
     */
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 响应业务状态
     */
    private long status;

    /**
     * 响应消息
     */
    private String msg;
    /**
     * 响应中的数据
     */
    private Object data;

    public static Result build(Long status, String msg, Object data) {
        return new Result(status, msg, data);
    }

    public static Result ok() {
        return new Result(null);
    }

    public static Result ok(Object data) {
        return new Result(data);
    }


    public static Result unAuth() {
        return new Result(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), "");
    }

    public static Result error() {
        return new Result(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage(), null);
    }

    public static Result error(Object msg) {
        return new Result(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage(), msg);
    }

    public Result() {
    }

    public static Result build(Integer status, String msg) {
        return new Result(ResultCode.UNAUTHORIZED.getCode(), msg, null);
    }

    public Result(Long status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public Result(Object data) {
        this.status = ResultCode.SUCCESS.getCode();
        this.msg = ResultCode.SUCCESS.getMessage();
        this.data = data;
    }

    /**
     * 将json结果集转化为Result对象
     *
     * @param jsonData json数据 传的是Result的对象的Json字符串
     * @param clazz    TaotaoResult中的object类型
     * @return
     */
    public static Result formatToPojo(String jsonData, Class<?> clazz) {
        try {
            if (clazz == null) {
                return MAPPER.readValue(jsonData, Result.class);
            }
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (clazz != null) {
                if (data.isObject()) {
                    obj = MAPPER.readValue(data.traverse(), clazz);
                } else if (data.isTextual()) {
                    obj = MAPPER.readValue(data.asText(), clazz);
                }
            }
            return build((long) jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 没有object对象的转化
     *
     * @param json
     * @return
     */
    public static Result format(String json) {
        try {
            return MAPPER.readValue(json, Result.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Object是集合转化
     *
     * @param jsonData 传的是Result的对象的Json字符串
     *                 json数据
     * @param clazz    集合中的类型
     * @return
     */
    public static Result formatToList(String jsonData, Class<?> clazz) {
        try {
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (data.isArray() && data.size() > 0) {
                obj = MAPPER.readValue(data.traverse(),
                        MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
            }
            return build((long) jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }
}


package com.uncle.jwt.util;

/**
 * @program: spring-security-jwt
 * @description:
 * @author: 步尔斯特
 * @create: 2021-08-08 15:33
 */
public enum ResultCode {
    SUCCESS(200, "操作成功"),
    FAILED(500, "操作失败"),
    VALIDATE_FAILED(404, "参数检验失败"),
    UNAUTHORIZED(401, "暂未登录或token已经过期"),
    FORBIDDEN(403, "没有相关权限");
    private long code;
    private String message;

    private ResultCode(long code, String message) {
        this.code = code;
        this.message = message;
    }

    public long getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

启动类

略…

测试

GET http://localhost:8080/postAuthorize
Accept: application/json
token: eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSykstLc5PK1HSUSotTi0Kys9JBQoG-fu4xju6-Hr66YCZocGuQUAVqRUFSlaGZkYWJhYWBhYGtQAtDsvwRAAAAA.6o-vyj5TmIhh8Q8kDmoBOif203HhvvtVKZrVXERa6osP9dp0zSS9HRG1CP6JGz1DC-tJX2hN_ARCWHAW7egroA

###
GET http://localhost:8080/api/hello
Accept: application/json
Cache-Control: no-cache

###
POST http://localhost:8080/login?username=neusoft&password=neusoft
Accept: */*
Cache-Control: no-cache

###
GET http://localhost:8080/admin
Accept: */*
Cache-Control: no-cache
token: eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSykstLc5PK1HSUSotTi0Kys9JBQoG-fu4xju6-Hr66YCZocGuQUAVqRUFSlaGZkYWJhYWBhYGtQAtDsvwRAAAAA.6o-vyj5TmIhh8Q8kDmoBOif203HhvvtVKZrVXERa6osP9dp0zSS9HRG1CP6JGz1DC-tJX2hN_ARCWHAW7egroA


测试结果

POST http://localhost:8080/login?username=neusoft&password=neusoft

HTTP/1.1 200 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Content-Length: 521
Date: Sun, 08 Aug 2021 09:58:51 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "status": 200,
  "msg": "操作成功",
  "data": {
    "loginName": "neusoft",
    "user": {
      "password": null,
      "username": "neusoft",
      "authorities": [
        {
          "authority": "ROLE_ADMIN"
        },
        {
          "authority": "ROLE_USER"
        }
      ],
      "accountNonExpired": true,
      "accountNonLocked": true,
      "credentialsNonExpired": true,
      "enabled": true
    },
    "token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSykstLc5PK1HSUSotTi0Kys9JBQoG-fu4xju6-Hr66YCZocGuQUAVqRUFSlaGZkYWpgbGhsYGtQBhPtZ5RAAAAA.G7XsVdsuWpGqI9Elk9-pwDmRhZExixrcmdZfUP-G2lbVekK6pCP8S519OCv5NBlT8nOGoPr37y_ArHqmtJq6Jg"
  }
}

Response code: 200; Time: 355ms; Content length: 513 bytes

GET http://localhost:8080/hello

HTTP/1.1 200 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Content-Length: 57
Date: Sun, 08 Aug 2021 10:01:01 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "status": 500,
  "msg": "操作失败",
  "data": "鉴权失败"
}

Response code: 200; Time: 238ms; Content length: 41 bytes

GET http://localhost:8080/admin

HTTP/1.1 200 
Set-Cookie: JSESSIONID=5D0735B1D787417D2443933A934E8AD1; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 8
Date: Sun, 08 Aug 2021 10:01:24 GMT
Keep-Alive: timeout=60
Connection: keep-alive

admin!!!

Response code: 200; Time: 328ms; Content length: 8 bytes

Cookies are preserved between requests:
> /Users/uncle/Downloads/spring-security-jwt/.idea/httpRequests/http-client.cookies

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security JWT实现是指使用JWT(JSON Web Token)作为身份验证和授权机制的Spring Security解决方案。Spring SecurityJWT提供了自动化配置,使得使用JWT进行身份验证和授权变得更加简单和高效。通过配置JwtAuthenticationTokenFilter,可以实现JWT的验证和解析。同时,可以通过RestfulAccessDeniedHandler和RestAuthenticationEntryPoint来处理登录校验和权限校验的逻辑。使用JWT实现Spring Security解决方案可以提供更加强大和灵活的身份验证和授权功能。 [2 [3<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [厉害,我带的实习生仅用四步就整合好SpringSecurity+JWT实现登录认证](https://blog.csdn.net/qing_gee/article/details/124016059)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [单点登录SSO解决方案之SpringSecurity+JWT实现.docx](https://download.csdn.net/download/njbaige/34581331)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值