spring security 6.3.3 + java-jwt 3.10.3 模拟前后端分离使用token验证登录
例子中实现了用户通过security验证成功后获取后端返回的token,在token有效期内可以访问受保护的 /user_request 接口,通过token验证后获取后端返回的数据
项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
< project xmlns = " http://maven.apache.org/POM/4.0.0" xmlns: xsi= " http://www.w3.org/2001/XMLSchema-instance"
xsi: schemaLocation= " http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" >
< modelVersion> 4.0.0</ modelVersion>
< parent>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-parent</ artifactId>
< version> 3.3.3</ version>
< relativePath/>
</ parent>
< groupId> com.security</ groupId>
< artifactId> demo</ artifactId>
< version> 0.0.1-SNAPSHOT</ version>
< name> demo</ name>
< description> Demo project for Spring Boot</ description>
< url/>
< licenses>
< license/>
</ licenses>
< developers>
< developer/>
</ developers>
< scm>
< connection/>
< developerConnection/>
< tag/>
< url/>
</ scm>
< properties>
< java.version> 21</ java.version>
</ properties>
< dependencies>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-security</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-thymeleaf</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
</ dependency>
< dependency>
< groupId> org.mybatis.spring.boot</ groupId>
< artifactId> mybatis-spring-boot-starter</ artifactId>
< version> 3.0.3</ version>
</ dependency>
< dependency>
< groupId> org.thymeleaf.extras</ groupId>
< artifactId> thymeleaf-extras-springsecurity6</ artifactId>
</ dependency>
< dependency>
< groupId> com.alibaba.fastjson2</ groupId>
< artifactId> fastjson2</ artifactId>
< version> 2.0.51</ version>
</ dependency>
< dependency>
< groupId> com.mysql</ groupId>
< artifactId> mysql-connector-j</ artifactId>
< scope> runtime</ scope>
</ dependency>
< dependency>
< groupId> com.auth0</ groupId>
< artifactId> java-jwt</ artifactId>
< version> 3.10.3</ version>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-data-redis</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-test</ artifactId>
< scope> test</ scope>
</ dependency>
< dependency>
< groupId> org.mybatis.spring.boot</ groupId>
< artifactId> mybatis-spring-boot-starter-test</ artifactId>
< version> 3.0.3</ version>
< scope> test</ scope>
</ dependency>
< dependency>
< groupId> org.springframework.security</ groupId>
< artifactId> spring-security-test</ artifactId>
< scope> test</ scope>
</ dependency>
</ dependencies>
< build>
< plugins>
< plugin>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-maven-plugin</ artifactId>
</ plugin>
</ plugins>
</ build>
< repositories>
< repository>
< id> maven_central</ id>
< name> Maven Central</ name>
< url> https://repo.maven.apache.org/maven2/</ url>
</ repository>
</ repositories>
</ project>
pojo - DbUser
package com. security. demo. pojo ;
import java. io. Serializable ;
public class DbUser implements Serializable {
private int id;
private String username;
private String password;
private String source;
public int getId ( ) {
return id;
}
public void setId ( int id) {
this . id = id;
}
public String getUsername ( ) {
return username;
}
public void setUsername ( String username) {
this . username = username;
}
public String getPassword ( ) {
return password;
}
public void setPassword ( String password) {
this . password = password;
}
public String getSource ( ) {
return source;
}
public void setSource ( String source) {
this . source = source;
}
@Override
public String toString ( ) {
return "DbUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", source='" + source + '\'' +
'}' ;
}
}
pojo - LoginUser
package com. security. demo. pojo ;
import org. springframework. security. core. GrantedAuthority ;
import org. springframework. security. core. userdetails. UserDetails ;
import java. io. Serializable ;
import java. util. Collection ;
import java. util. List ;
public class LoginUser implements Serializable , UserDetails {
private DbUser dbUser;
public DbUser getUser ( ) {
return dbUser;
}
public void setUser ( DbUser dbUser) {
this . dbUser = dbUser;
}
@Override
public Collection < ? extends GrantedAuthority > getAuthorities ( ) {
return List . of ( ) ;
}
@Override
public String getPassword ( ) {
return dbUser. getPassword ( ) ;
}
@Override
public String getUsername ( ) {
return dbUser. getUsername ( ) ;
}
@Override
public boolean isAccountNonExpired ( ) {
return UserDetails . super . isAccountNonExpired ( ) ;
}
@Override
public boolean isAccountNonLocked ( ) {
return UserDetails . super . isAccountNonLocked ( ) ;
}
@Override
public boolean isCredentialsNonExpired ( ) {
return UserDetails . super . isCredentialsNonExpired ( ) ;
}
@Override
public boolean isEnabled ( ) {
return UserDetails . super . isEnabled ( ) ;
}
@Override
public String toString ( ) {
return "LoginUser{" + "DbUser=" + dbUser + "}" ;
}
}
pojo - ResultData
package com. security. demo. pojo ;
import java. io. Serializable ;
public class ResultData implements Serializable {
private int code;
private String msg;
private Object data;
private ResultData ( ) { }
public int getCode ( ) {
return code;
}
public String getMsg ( ) {
return msg;
}
public Object getData ( ) {
return data;
}
public static class ResultDataBuild {
private ResultDataBuild ( ) { }
ResultData resultData = new ResultData ( ) ;
public ResultDataBuild setCode ( int code) {
resultData. code = code;
return this ;
}
public ResultDataBuild setMsg ( String msg) {
resultData. msg = msg;
return this ;
}
public ResultDataBuild setData ( Object object) {
resultData. data = object;
return this ;
}
public ResultData build ( ) {
return resultData;
}
}
public static ResultDataBuild builder ( ) {
return new ResultDataBuild ( ) ;
}
}
LoginUserAuthorization
package com. security. demo. config. authentication ;
import com. security. demo. dao. DemoDao ;
import com. security. demo. pojo. LoginUser ;
import com. security. demo. pojo. DbUser ;
import jakarta. annotation. Resource ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
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. Component ;
import java. util. Objects ;
@Component
public class LoginUserAuthorization implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory . getLogger ( LoginUserAuthorization . class ) ;
@Resource
private DemoDao demoDao;
@Override
public UserDetails loadUserByUsername ( String username) {
DbUser dbUser = null ;
try {
dbUser = demoDao. queryByName ( username) ;
} catch ( Exception e) {
LOGGER . error ( "在这里准备记录一个可能不会发生的错误" ) ;
}
if ( Objects . isNull ( dbUser) ) {
LOGGER . error ( "库里不存在该用户名字" ) ;
throw new UsernameNotFoundException ( "用户名或密码错误" ) ;
}
LoginUser loginUser = new LoginUser ( ) ;
loginUser. setUser ( dbUser) ;
return loginUser;
}
}
TokenAuthorization
package com. security. demo. config. authentication ;
import com. alibaba. fastjson2. JSON ;
import com. alibaba. fastjson2. JSONObject ;
import com. security. demo. utils. RedisUtil ;
import com. security. demo. utils. TokenUtil ;
import jakarta. annotation. Resource ;
import jakarta. servlet. FilterChain ;
import jakarta. servlet. ServletException ;
import jakarta. servlet. http. HttpServletRequest ;
import jakarta. servlet. http. HttpServletResponse ;
import org. springframework. security. authentication. UsernamePasswordAuthenticationToken ;
import org. springframework. security. core. context. SecurityContextHolder ;
import org. springframework. stereotype. Component ;
import org. springframework. util. StringUtils ;
import org. springframework. web. filter. OncePerRequestFilter ;
import java. io. IOException ;
import java. util. Objects ;
@Component
public class TokenAuthorization extends OncePerRequestFilter {
@Resource
private RedisUtil redisUtil;
@Override
protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException , IOException {
String token = request. getHeader ( "token" ) ;
if ( ! StringUtils . hasText ( token) ) {
filterChain. doFilter ( request, response) ;
return ;
}
String authorization = TokenUtil . parseToken ( token) ;
Object value = redisUtil. getKey ( authorization) ;
if ( Objects . isNull ( authorization) && Objects . isNull ( value) ) {
throw new RuntimeException ( "token无效" ) ;
}
JSONObject object = JSON . parseObject ( value. toString ( ) ) ;
UsernamePasswordAuthenticationToken authenticationToken;
authenticationToken = new UsernamePasswordAuthenticationToken ( object, null , null ) ;
SecurityContextHolder . getContext ( ) . setAuthentication ( authenticationToken) ;
filterChain. doFilter ( request, response) ;
}
}
RedisConfig
package com. security. demo. config ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. data. redis. connection. RedisConnectionFactory ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. data. redis. serializer. StringRedisSerializer ;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate < String , Object > redisTemplate ( RedisConnectionFactory redisConnectionFactory) {
RedisTemplate < String , Object > template = new RedisTemplate < > ( ) ;
template. setConnectionFactory ( redisConnectionFactory) ;
template. setKeySerializer ( new StringRedisSerializer ( ) ) ;
template. setValueSerializer ( new StringRedisSerializer ( ) ) ;
template. afterPropertiesSet ( ) ;
return template;
}
}
SecurityConfig
package com. security. demo. config ;
import com. security. demo. config. authentication. LoginUserAuthorization ;
import com. security. demo. config. authentication. TokenAuthorization ;
import jakarta. annotation. Resource ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. security. authentication. AuthenticationManager ;
import org. springframework. security. authentication. ProviderManager ;
import org. springframework. security. authentication. dao. DaoAuthenticationProvider ;
import org. springframework. security. config. annotation. web. builders. HttpSecurity ;
import org. springframework. security. config. annotation. web. configurers. AbstractHttpConfigurer ;
import org. springframework. security. crypto. bcrypt. BCryptPasswordEncoder ;
import org. springframework. security. crypto. password. PasswordEncoder ;
import org. springframework. security. web. SecurityFilterChain ;
import org. springframework. security. web. authentication. UsernamePasswordAuthenticationFilter ;
@Configuration
public class SecurityConfig {
@Resource
private LoginUserAuthorization loginUserAuthorization;
@Resource
private TokenAuthorization tokenAuthorization;
@Bean
public PasswordEncoder passwordEncoder ( ) {
return new BCryptPasswordEncoder ( ) ;
}
@Bean
public AuthenticationManager authenticationManager ( ) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider ( ) ;
provider. setUserDetailsService ( loginUserAuthorization) ;
provider. setPasswordEncoder ( passwordEncoder ( ) ) ;
return new ProviderManager ( provider) ;
}
@Bean
public SecurityFilterChain filterChain ( HttpSecurity http) throws Exception {
http. csrf ( AbstractHttpConfigurer :: disable ) ;
http. authorizeHttpRequests ( r -> r. requestMatchers ( "/demo/user_login" , "/demo/register" ) . permitAll ( )
. anyRequest ( ) . authenticated ( ) ) ;
http. addFilterBefore ( tokenAuthorization, UsernamePasswordAuthenticationFilter . class ) ;
return http. build ( ) ;
}
}
TokenUtil
package com. security. demo. utils ;
import com. alibaba. fastjson2. JSON ;
import com. alibaba. fastjson2. JSONObject ;
import com. auth0. jwt. JWT ;
import com. auth0. jwt. JWTVerifier ;
import com. auth0. jwt. algorithms. Algorithm ;
import com. auth0. jwt. exceptions. JWTVerificationException ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
import java. nio. charset. StandardCharsets ;
import java. util. Base64 ;
import java. util. Date ;
public class TokenUtil {
private static final Logger LOGGER = LoggerFactory . getLogger ( TokenUtil . class ) ;
public static final String SECRET = "ABCDEFG_HIGKLMN_12345_67890" ;
public static final long EXPIRATION = 3600000 ;
private static final Algorithm ALGORITHM = Algorithm . HMAC256 ( SECRET ) ;
public static String generateToken ( String id) {
String token = null ;
try {
token = JWT . create ( )
. withExpiresAt ( new Date ( System . currentTimeMillis ( ) + EXPIRATION ) )
. withClaim ( "Authorization" , id)
. sign ( ALGORITHM ) ;
} catch ( Exception e) {
LOGGER . error ( "生成JWT出现异常" ) ;
return token;
}
return token;
}
public static String parseToken ( String token) {
String authorization = null ;
try {
JWTVerifier verifier = JWT . require ( ALGORITHM ) . build ( ) ;
String payload = verifier. verify ( token) . getPayload ( ) ;
byte [ ] decode = Base64 . getDecoder ( ) . decode ( payload) ;
String string = new String ( decode, StandardCharsets . UTF_8 ) ;
JSONObject jsonObject = JSON . parseObject ( string) ;
authorization = jsonObject. get ( "Authorization" ) . toString ( ) ;
} catch ( JWTVerificationException e) {
LOGGER . error ( "验证token出现异常" ) ;
return authorization;
}
return authorization;
}
}
RedisUtil
package com. security. demo. utils ;
import jakarta. annotation. Resource ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. stereotype. Component ;
import java. util. Objects ;
@Component
public class RedisUtil {
private static final Logger LOGGER = LoggerFactory . getLogger ( RedisUtil . class ) ;
@Resource
private RedisTemplate < String , Object > redisTemplate;
public boolean setKeyValue ( String key, Object value) {
boolean result = false ;
try {
redisTemplate. opsForValue ( ) . set ( key, value) ;
result = true ;
} catch ( Exception e) {
LOGGER . error ( "Redis在存放时出现异常" ) ;
}
return result;
}
public Object getKey ( String key) {
Object object = null ;
if ( Objects . isNull ( key) || key. isBlank ( ) ) {
return object;
}
try {
object = redisTemplate. opsForValue ( ) . get ( key) ;
} catch ( Exception e) {
LOGGER . error ( "Redis取值出现异常" ) ;
}
return object;
}
public boolean deleteKey ( String key) {
boolean result = false ;
try {
result = redisTemplate. delete ( key) ;
} catch ( Exception e) {
LOGGER . error ( "Redis册除值出现异常" ) ;
}
return result;
}
}
DemoController
package com. security. demo. controller ;
import com. alibaba. fastjson2. JSON ;
import com. security. demo. pojo. DbUser ;
import com. security. demo. pojo. ResultData ;
import com. security. demo. service. DemoService ;
import jakarta. annotation. Resource ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RequestHeader ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
import java. util. List ;
@RestController
@RequestMapping ( "/demo" )
public class DemoController {
private static final Logger LOGGER = LoggerFactory . getLogger ( DemoController . class ) ;
@Resource
private DemoService demoService;
@RequestMapping ( "/list" )
public String list ( ) {
List < DbUser > dbUsers = demoService. queryAll ( ) ;
return JSON . toJSONString ( dbUsers) ;
}
@RequestMapping ( "/register" )
public String register ( @RequestBody DbUser dbUser) {
String username = dbUser. getUsername ( ) ;
int row = demoService. register ( dbUser) ;
if ( row == 0 ) {
username = username. concat ( ": 注册失败,用户名已存在" ) ;
LOGGER . error ( "{}:" , username) ;
}
if ( row == 1 ) {
username = username. concat ( ": 注册成功" ) ;
LOGGER . info ( "{}:" , username) ;
}
return username;
}
@RequestMapping ( "/user_login" )
public ResultData login ( @RequestBody DbUser dbUser) {
return demoService. login ( dbUser) ;
}
@RequestMapping ( "/user_request" )
public ResultData requestData ( @RequestHeader ( "token" ) String token) {
return demoService. requestData ( token) ;
}
}
DemoService
package com. security. demo. service ;
import com. security. demo. pojo. DbUser ;
import com. security. demo. pojo. ResultData ;
import java. util. List ;
public interface DemoService {
List < DbUser > queryAll ( ) ;
int register ( DbUser dbUser) ;
ResultData login ( DbUser dbUser) ;
ResultData requestData ( String token) ;
}
DemoServiceImpl
package com. security. demo. service. impl ;
import com. alibaba. fastjson2. JSON ;
import com. alibaba. fastjson2. JSONObject ;
import com. security. demo. dao. DemoDao ;
import com. security. demo. pojo. DbUser ;
import com. security. demo. pojo. ResultData ;
import com. security. demo. service. DemoService ;
import com. security. demo. utils. RedisUtil ;
import com. security. demo. utils. TokenUtil ;
import jakarta. annotation. Resource ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
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. crypto. password. PasswordEncoder ;
import org. springframework. stereotype. Service ;
import java. util. List ;
import java. util. Map ;
import java. util. Objects ;
import java. util. UUID ;
@Service
public class DemoServiceImpl implements DemoService {
private static final Logger LOGGER = LoggerFactory . getLogger ( DemoServiceImpl . class ) ;
@Resource
private DemoDao demoDao;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private AuthenticationManager authenticationManager;
@Resource
private RedisUtil redisUtil;
@Override
public List < DbUser > queryAll ( ) {
return demoDao. queryAll ( ) ;
}
@Override
public int register ( DbUser dbUser) {
int row = 0 ;
if ( checkUserNameIsExist ( dbUser. getUsername ( ) ) ) {
return row;
}
try {
String password = dbUser. getPassword ( ) ;
String encode = passwordEncoder. encode ( password) ;
dbUser. setPassword ( encode) ;
row = demoDao. register ( dbUser) ;
} catch ( Exception e) {
LOGGER . error ( "用户在注册时出现异常" ) ;
}
return row;
}
@Override
public ResultData login ( DbUser dbUser) {
try {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken ( dbUser. getUsername ( ) , dbUser. getPassword ( ) ) ;
authenticationManager. authenticate ( authenticationToken) ;
} catch ( AuthenticationException e) {
return ResultData . builder ( ) . setCode ( 403 ) . setMsg ( "用户名或密码错误" ) . setData ( null ) . build ( ) ;
}
String uuid = UUID . randomUUID ( ) . toString ( ) ;
String token = TokenUtil . generateToken ( uuid) ;
redisUtil. setKeyValue ( uuid, JSON . toJSONString ( dbUser) ) ;
return ResultData . builder ( ) . setCode ( 200 ) . setMsg ( "登录成功并获取token" ) . setData ( Map . of ( "token" , token) ) . build ( ) ;
}
@Override
public ResultData requestData ( String token) {
String authorization = TokenUtil . parseToken ( token) ;
Object object = redisUtil. getKey ( authorization) ;
JSONObject jo = JSON . parseObject ( object. toString ( ) ) ;
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken ( jo. get ( "username" ) , jo. get ( "password" ) ) ;
Authentication authenticate = authenticationManager. authenticate ( authenticationToken) ;
if ( Objects . isNull ( authenticate) ) {
throw new RuntimeException ( "用户信息错误" ) ;
}
List < DbUser > dbUsers = demoDao. queryAll ( ) ;
return ResultData . builder ( ) . setCode ( 200 ) . setMsg ( "查到所有信息" ) . setData ( Map . of ( "userAll" , dbUsers) ) . build ( ) ;
}
private boolean checkUserNameIsExist ( String username) {
boolean result = false ;
try {
DbUser dbUser = demoDao. checkUserByName ( username) ;
if ( ! Objects . isNull ( dbUser) ) {
result = true ;
LOGGER . error ( "新用户名字已存在" ) ;
}
} catch ( Exception e) {
LOGGER . error ( "在这里准备记录一个可能不会发生的错误" ) ;
}
return result;
}
}
DemoDao
package com. security. demo. dao ;
import com. security. demo. pojo. DbUser ;
import org. apache. ibatis. annotations. Mapper ;
import java. util. List ;
@Mapper
public interface DemoDao {
List < DbUser > queryAll ( ) ;
DbUser queryByName ( String username) ;
int register ( DbUser dbUser) ;
DbUser checkUserByName ( String username) ;
}