您现在的位置是:亿华云 > 人工智能

SpringSecurity结合JWT实现前后端分离的后端授权

亿华云2025-10-03 06:57:13【人工智能】7人已围观

简介SpringSecurity-14-SpringSecurity结合JWT实现前后端分离的后端授权。什么是JWTJWT是JSON WEB TOKEN的缩写,它是基于RFC 7519标准定义的一种可以安

SpringSecurity-14-SpringSecurity结合JWT实现前后端分离的结合后端授权。

什么是现前JWT

JWT是JSON WEB TOKEN的缩写,它是后端基于RFC 7519标准定义的一种可以安全传输的JSON对象,因为使用了数字签名,分离所以可以信任。后端授

JWT的结合组成JWT token的格式:header.payload.signature。header中用于存放签名的现前生成算法{ "alg": "HS512"}。payload用于存放用户名、后端token的分离生成时间和过期时间{ "sub":"admin","created":1489079981393,"exp":1489684781}。signature为以header和payload生成的后端授签名,一旦header和payload被篡改,结合验证将失败//secret为加密算法的现前密钥。String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),后端secret)。JWT实例

这是分离一个JWT的字符串。

eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2NDg5ODg1MjAsInN1YiI6ImFkbWluIiwiY3JlYXRlZCI6MTY0ODk4NDkyMDQyNX0.P8YJ5AhcKATEpUmdtSmzGXcdDacESZ2jqU20JpjCqZOqy5AEE2uelYtay--Kg2wRWFx3bBhf9A5Jbv2S8fbs_A

可以在该网站上获得解析结果:https://jwt.io/。后端授

编码实现

环境准备工作建立Spring Boot项目并集成了Spring Security,项目可以正常启动。通过controller写一个HTTP的GET方法服务接口,网站模板比如:“/student/selectall”。实现最基本的动态数据验证及权限分配,即实现UserDetailsService接口。这两个接口都是向Spring Security提供用户、角色、权限等校验信息的接口。如果你学习过Spring Security的formLogin登录模式,请将HttpSecurity配置中的formLogin()配置段全部去掉。因为JWT完全使用JSON接口,没有from表单提交。HttpSecurity配置中一定要加上csrf().disable(),即暂时关掉跨站攻击CSRF的防御。这样是不安全的,我们后续章节再做处理。

以上实现可以去查看我的专题SpringBoot和SpringSecurity进行查看。

在pom.xml中添加项目依赖

org.springframework.boot

spring-boot-starter-security

io.jsonwebtoken

jjwt

0.9.0

org.springframework.boot

spring-boot-starter-web

mysql

mysql-connector-java

runtime

com.baomidou

mybatis-plus-boot-starter

3.5.1

cn.hutool

hutool-all

5.5.7

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.springframework.security

spring-security-test

test

在application.yml中加入如下自定义一些关于JWT的配置jwt:

header: JWTName

secret: springkhbd

expiration: 360jwt.header的value是Http的header中存储JWT的名称,名字可读性越差越安全。jwt.secret用来对JWT基础信息进行加密和解密的密匙。亿华云计算jwt.expiration用来设置JWT令牌的有效时间。添加JWT token的工具类JwtTokenUtil

JwtTokenUtil用于生成和解析JWT token的工具类。

主要方法:

generateToken(UserDetails userDetails):根据用户信息生成token令牌。getUserNameFromToken(String token):根据token令牌获取用户名。validateToken(String token, UserDetails userDetails):判断用户是否过期。refreshToken(String token):

根据token属性token的过期时间。package com.security.learn.util;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import lombok.Data;

import lombok.extern.slf4j.Slf4j;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.stereotype.Component;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

@Data

@Slf4j

@ConfigurationProperties(prefix = "jwt")

@Component

public class JwtTokenUtil {

private static final String CLAIM_KEY_USERNAME = "sub";

private static final String CLAIM_KEY_CREATED = "created";

private String secret;

private Long expiration;

private String header;

/

**

* 生成token令牌

*

* @param userDetails 用户

* @return 令token牌

*/

public String generateToken(UserDetails userDetails) {

Mapclaims = new HashMap<>(2);

claims.put(CLAIM_KEY_CREATED, userDetails.getUsername());

claims.put(CLAIM_KEY_CREATED, new Date());

//生成Token

return generateToken(claims);

}

/

**

* 从claims生成令牌

* @param claims

* @return

*/

private String generateToken(Mapclaims) {

return Jwts.builder()

.setClaims(claims)

.setExpiration(generateExpirationDate())

.signWith(SignatureAlgorithm.HS512, secret)

.compact();

}

/

**

* 从Token中获取用户名称

* @param token

* @return

*/

public String getUserNameFromToken(String token) {

String username;

try {

Claims claims = getClaimsFromToken(token);

username = claims.getSubject();

} catch (Exception e) {

username = null;

}

return username;

}

/

**

* 从令牌中获取数据声明,如果看不懂就看谁调用它

*

* @param token 令牌

* @return 数据声明

*/

private Claims getClaimsFromToken(String token) {

Claims claims;

try {

claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

} catch (Exception e) {

log.info("JWT格式验证失败:{ }",token);

claims = null;

}

return claims;

}

/

**

* 生成token的过期时间

*/

private Date generateExpirationDate() {

return new Date(System.currentTimeMillis() + expiration * 1000);

}

/

**

* 根据token过去过期时间

* @param token

* @return

*/

private Date getExpiredDateFromToken(String token) {

Claims claims = getClaimsFromToken(token);

return claims.getExpiration();

}

/

**

*

* 验证Token是否过期

* @param token

* @param userDetails

* @return true表示没有过期,false表示过期

*/

public boolean validateToken(String token, UserDetails userDetails) {

String username = getUserNameFromToken(token);

return username.equals(userDetails.getUsername()) && !isTokenExpired(token);

}

/

**

* 判断令牌是否过期

* @param token

* @return

*/

public Boolean isTokenExpired(String token) {

try {

Claims claims = getClaimsFromToken(token);

Date expiration = claims.getExpiration();

return expiration.before(new Date());

} catch (Exception e) {

return false;

}

}

/

**

* 判断token是否可以刷新

* @param token

* @return

*/

public boolean canRefresh(String token) {

return !isTokenExpired(token);

}

/

**

* 刷新token

*/

public String refreshToken(String token) {

Claims claims = getClaimsFromToken(token);

claims.put(CLAIM_KEY_CREATED, new Date());

return generateToken(claims);

}

}UserDetailsService接口的实现@Component("myUserDetailsService")

@Slf4j

public class MyUserDetailsService implements UserDetailsService {

@Autowired

private UserMapper userMapper;

@Autowired

private AuthoritiesMapper authoritiesMapper;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

log.info("认证请求: "+ username);

QueryWrapperwrapper = new QueryWrapper<>();

wrapper.eq("username",username);

ListuserEntities = userMapper.selectList(wrapper);

if (userEntities.size()>0){

QueryWrapper wrapper1 = new QueryWrapper<>();

wrapper.eq("userId", userEntities.get(0).getId());

List authorities = authoritiesMapper.selectList(wrapper1);

return new User(username, userEntities.get(0).getPassword(), AuthorityUtils.createAuthorityList(authorities.toString()));

}

return null;

}

}

开发登录接口(获取Token的接口)

JwtAdminService接口public interface JwtAdminService {

/

**

* 登录功能

* @param username 用户名

* @param password 密码

* @return 生成的JWT的token

*/

String login(String username, String password);

/

**

* 刷新Token

* @param oldToken

* @return

*/

String refreshToken(String oldToken);

}JwtAdminService接口实现@AllArgsConstructor

@Slf4j

@Service

public class JwtAdminServiceImpl implements JwtAdminService {

private final UserDetailsService customUserDetailsService;

private final JwtTokenUtil jwtTokenUtill;

private final PasswordEncoder passwordEncoder;

/

**

* 根据用户名密码登录时生成Token

* @param username 用户名

* @param password 密码

* @return

*/

@Override

public String login(String username, String password) {

try{

//根据用户名获取 用户信息

UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);

if(!passwordEncoder.matches(password,userDetails.getPassword())){

throw new BadCredentialsException("密码不正确");

}

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);

SecurityContextHolder.getContext().setAuthentication(token);

}catch (AuthenticationException e){

log.error("用户名或者密码不正确");

}

//生成JWT

UserDetails userDetails = customUserDetailsService.loadUserByUsername( username );

return jwtTokenUtill.generateToken(userDetails);

}

@Override

public String refreshToken(String oldToken) {

if (!jwtTokenUtill.isTokenExpired(oldToken)) {

return jwtTokenUtill.refreshToken(oldToken);

}

return null;

}

}JwtAuthController的实现"/login"接口用于登录验证,并且生成JWT返回给客户端。"/refreshtoken"接口用于刷新JWT,更新JWT令牌的有效期。@RestController

public class JwtAuthController {

@Resource

private JwtAdminService jwtAuthService;

@PostMapping(value = "/login")

public Result login(@RequestBody Mapmap) throws Exception {

String username = map.get("username");

String password = map.get("password");

if (StrUtil.isEmpty(username) || (StrUtil.isEmpty(password))) {

return Result.fail("用户名密码不能为空");

}

try{

return Result.data( jwtAuthService.login(username, password));

}catch(Exception e){

return Result.fail(e.getMessage());

}

}

@PostMapping(value = "/refreshtoken")

public String refresh(@RequestHeader("${ jwt.header}") String token) {

return jwtAuthService.refreshToken(token);

}

}添加SpringSecurity的配置类LearnSrpingSecurityimport com.security.learn.filter.JwtAuthenticationTokenFilter;

import com.security.learn.handler.RestAuthenticationEntryPoint;

import com.security.learn.handler.RestfulAccessDeniedHandler;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.config.http.SessionCreationPolicy;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/

**

* 安全配置类

*/

@EnableWebSecurity

public class LearnSrpingSecurity extends WebSecurityConfigurerAdapter {

@Autowired

private UserDetailsService myUserDetailsService;

@Autowired

private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Autowired

private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

@Autowired

private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

/

**

* 认证管理器

* 1.认证信息提供方式(用户名、密码、当前用户的资源权限)

* 2.可采用内存存储方式,也可能采用数据库方式等

* @param auth

* @throws Exception

*/

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(myUserDetailsService);

}

/

**

* 资源权限配置(过滤器链):

* 1、被拦截的源码库资源

* 2、资源所对应的角色权限

* 3、定义认证方式:httpBasic 、httpForm

* 4、定制登录页面、登录请求地址、错误处理方式

* 5、自定义 spring security 过滤器

* @param http

* @throws Exception

*/

@Override

protected void configure(HttpSecurity http) throws Exception {

http.csrf().disable() //禁用跨站csrf攻击防御,后面的章节会专门讲解

.sessionManagement()// 基于token,所以不需要session

.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

.and()

.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)

.authorizeRequests()

.antMatchers("/login").permitAll()//不需要通过登录验证就可以被访问的资源路径

.anyRequest().authenticated();

//添加自定义未授权和未登录结果返回

http.exceptionHandling()

.accessDeniedHandler(restfulAccessDeniedHandler)

.authenticationEntryPoint(restAuthenticationEntryPoint);

}

}相关依赖以及方法说明configure(HttpSecurity http):资源权限配置(过滤器链)、jwt过滤器及出异常后的处理器。configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder。RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果。RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果。UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现。JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。configure(HttpSecurity http),主要配置:

将我们的自定义jwtAuthenticationTokenFilter,加载到UsernamePasswordAuthenticationFilter的前面。因为我们使用了JWT,表明了我们的应用是一个前后端分离的应用,所以我们可以开启STATELESS禁止使用session。添加RestfulAccessDeniedHandler

当访问接口没有权限时,自定义的返回结果。

/

**

* 当访问接口没有权限时,自定义的返回结果

*/

@Component

public class RestfulAccessDeniedHandler implements AccessDeniedHandler {

@Override

public void handle(HttpServletRequest request,

HttpServletResponse response,

AccessDeniedException e) throws IOException, ServletException {

response.setCharacterEncoding("UTF-8");

response.setContentType("application/json");

response.getWriter().println(JSONUtil.parse(Result.fail(e.getMessage())));

response.getWriter().flush();

}

}添加RestAuthenticationEntryPoint

当用户未登录或者token失效访问接口时,自定义的返回结果。

/

**

* 当未登录或者token失效访问接口时,自定义的返回结果

*/

@Component

public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override

public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

response.setCharacterEncoding("UTF-8");

response.setContentType("application/json");

response.getWriter().println(JSONUtil.parse(Result.fail(authException.getMessage())));

response.getWriter().flush();

}

}添加JwtAuthenticationTokenFilter

在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。

@Slf4j

@Component

@AllArgsConstructor

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

private final UserDetailsService myUserDetailsService;

private final JwtTokenUtil jwtTokenUtil;

@Override

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

String jwt = request.getHeader(jwtTokenUtil.getHeader());

if(!StrUtil.isEmpty(jwt)){

//根据jwt获取用户名

String username = jwtTokenUtil.getUserNameFromToken(jwt);

log.info("校验username:{ }",username);

//如果可以正确从JWT中提取用户信息,并且该用户未被授权

if(!StrUtil.isEmpty(username) && SecurityContextHolder.getContext().getAuthentication()==null){

UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);

if(jwtTokenUtil.validateToken(jwt,userDetails)){

//给使用该JWT令牌的用户进行授权

UsernamePasswordAuthenticationToken authenticationToken

= new UsernamePasswordAuthenticationToken(userDetails,null,

userDetails.getAuthorities());

authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authenticationToken);

}

}

}

filterChain.doFilter(request, response);

}

}测试

测试登录接口,即:获取token的接口。输入正确的用户名、密码即可获取token。

使用不带token,但是不传递JWT令牌,结果是禁止访问。

使用不带token,携带JWT令牌。

很赞哦!(55)