一定要熟悉spring security原理和jwt无状态原理,理解了才知道代码作用。
在 Spring Security + JWT 认证流程中,通常的做法是:
- 用户提交用户名和密码
- Spring Security 认证管理器 (
AuthenticationManager
) 进行认证 - 如果认证成功,生成 JWT Token 并返回给用户
更详细一点
-
用户首次登录
- 发送
POST /login
请求,携带用户名 + 密码
authenticationManager.authenticate()
认证成功后,返回JWT
- 前端存储
JWT
(通常是localStorage
或sessionStorage
)
- 发送
-
用户访问受保护接口
- 前端在
Authorization
头中附带Bearer Token
- 过滤器
JWTFilter
解析JWT
,从数据库
加载UserDetails
SecurityContextHolder.setAuthentication()
认证成功,继续访问资源。
- 前端在
参考链接有:
spring security 超详细使用教程(接入springboot、前后端分离) - 小程xy - 博客园
SpringSecurity+jwt实现权限认证功能_spring security + jwt-CSDN博客
1.引入相关依赖。我使用的是springboot3.3.5 springsecurity是6.x的 jwt 0.12.6
XML
<dependencies>
<!--用于数据加密,默认启用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
</dependencies>
<!--依赖集中管理-->
<dependencyManagement>
<dependencies>
<!-- 使用jwt进行token验证,包括了三个依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependencies>
</dependencyManagement>
2.配置SecurityConfig.java
java
package com.x.x.x.config;
import com.x.x.x.filter.CustomFilter;
import com.x.x.x.filter.JwtAuthenticationTokenFilter;
import com.x.x.x.security.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
/**
* 用户名和密码也可以在application.properties中设置。
* @return
*/
@Bean
public UserDetailsService userDetailsService() {
// 创建基于内存的用户信息管理器
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// 创建UserDetails对象,用于管理用户名、用户密码、用户角色、用户权限等内容
manager.createUser(
User.withUsername("admin").password("yourpassword").roles("ADMIN").build()
);
return manager;
}
/**
* 认证管理。 jwt的用户验证
* @param authConfig
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
/**
* 认证的token过滤器
* @return
*/
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
return new JwtAuthenticationTokenFilter();
}
/**
* 密码加码
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
// 也可用有参构造,取值范围是 4 到 31,默认值为 10。数值越大,加密计算越复杂
return new BCryptPasswordEncoder();
}
/**
* 配置过滤链
* 配置自动注销功能必须在函数里加UserDetailsService userDetailsService,因为重写了使用数据库认证所以用baseuserserviceimpl
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsServiceImpl userDetailsService) throws Exception {
http
// 开启授权保护,配置请求授权规则
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login","/mylogin","/druid/**").permitAll() // 不需要认证的地址有哪些 ("/blog/**", "/public/**", "/about")
.anyRequest() // 对所有请求开启授权保护
.authenticated() // 已认证的请求会被自动授权
)
// 配置自定义登录页面
// 本处禁用前端页面,使用功能RESTful风格前后端分离,就是不用登录页面
.formLogin(form -> form.disable())
.httpBasic(Customizer -> Customizer.disable())
// 启用记住我功能。允许用户关闭浏览器后仍然保持登录状态,直到主动注销或者查出设定过期时间
//.rememberMe(Customizer.withDefaults())
.rememberMe(rememberMe -> rememberMe
.key("uniqueAndSecret") // 设置一个密钥
.tokenValiditySeconds(2 * 24 * 60 * 60) // 设置 RememberMe token 的有效期
.userDetailsService(userDetailsService) // 显式设置 UserDetailsService
)
// 配置注销功能
.logout(logout -> logout
.logoutUrl("/perform_logout") // 自定义注销请求路径
//.logoutSuccessUrl("/login?logout=true") // 注销成功后的跳转页面
.deleteCookies("JSESSIONID") // 删除指定的 Cookie
.permitAll() // 允许所有用户注销
)
.sessionManagement(session -> session
.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::changeSessionId) // 防止会话固定攻击
.maximumSessions(1) // 限制每个用户只能有一个活跃会话
.maxSessionsPreventsLogin(false)// 如果为 true,禁止新登录;为 false,允许新登录并终止旧会话
.expiredUrl("/login?session=expired") // 当会话过期时跳转到的页面
)
;
// 关闭 csrf CSRF(跨站请求伪造)是一种网络攻击,攻击者通过欺骗已登录用户,诱使他们在不知情的情况下向受信任的网站发送请求。
http.csrf(csrf -> csrf.disable());
// 注册自定义的过滤器CustomFilter
// 用于jwt 功能确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景
http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);
//已经在customfilter中重写 http.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//授权认证,基于角色在 Spring Security 6.x 版本中,antMatchers() 方法已被移除,取而代之的是使用新的基于 请求匹配器 (RequestMatchers) 的方法
/*http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有 ADMIN 角色可以访问
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 和 ADMIN 角色可以访问
.anyRequest().authenticated()); // 其他请求需要认证
//基于权限的授权,编辑权限还是只读等
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE") // 仅具有 EDIT_PRIVILEGE 权限的用户可以访问
.anyRequest().authenticated()); // 其他请求需要认证*/
return http.build();
}
}
3.重写loadUserByUsername的方法。
(1)UserDetailsImpl.java
java
package com.x.x.x.security.service.impl;
import com.x.x.x.entity.BaseUsers;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor // 这三个注解可以帮我们自动生成 get、set、有参、无参构造函数
public class UserDetailsImpl implements UserDetails {
private BaseUsers baseUsers;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getPassword() {
return baseUsers.getPassword();
}
@Override
public String getUsername() {
return baseUsers.getOaId();
}
@Override
public boolean isAccountNonExpired() { // 检查账户是否 没过期。
return true;
}
@Override
public boolean isAccountNonLocked() { // 检查账户是否 没有被锁定。
return true;
}
@Override
public boolean isCredentialsNonExpired() { //检查凭据(密码)是否 没过期。
return true;
}
@Override
public boolean isEnabled() { // 检查账户是否启用。
return true;
}
// 这个方法是 @Data注解 会自动帮我们生成,用来获取 loadUserByUsername 中最后我们返回的创建UserDetailsImpl对象时传入的User。
// 如果你的字段包含 username和password 的话可以用强制类型转换, 把 UserDetailsImpl 转换成 User。如果不能强制类型转换的话就需要用到这个方法了
public BaseUsers getUser() {
return baseUsers;
}
}
(2)UserDetailsServiceImpl.java
java
package com.x.x.x.security.service.impl;
import com.x.x.x.entity.BaseUsers;
import com.x.x.x.service.BaseUsersService;
import org.springframework.beans.factory.annotation.Autowired;
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.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private BaseUsersService baseUsersService;
/**
* 重写loadUserByUsername方法
* @param username the username identifying the user whose data is required.
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BaseUsers baseUsers = new BaseUsers();
baseUsers.setOaId(username);
List<BaseUsers> baseUsersList = baseUsersService.queryUsersList(baseUsers);
if (baseUsersList == null || baseUsersList.isEmpty()) {
System.out.println("-------------> loadUserByUsername验证失败, "+baseUsers.getOaId()+" 不存在!");
throw new UsernameNotFoundException(username);
}
return new UserDetailsImpl(baseUsersList.get(0)); // UserDetailsImpl 是我们实现的类
}
}
4.JwtAuthenticationProvider.java继承重新AuthenticationProvider的authenticate方法。这里注意可能未使用我们继承的userDetailsService,所以使用@Qualifier("")指定
java
package com.x.x.x.security.handler;
import io.micrometer.common.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("userDetailsServiceImpl")//需要指定注入的是那个类,避免报错。
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) {
String username = String.valueOf(authentication.getPrincipal());
String password = String.valueOf(authentication.getCredentials());
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
System.out.println("-------------> JwtAuthenticationProvider:"+userDetails.getUsername()+","+userDetails.getPassword());
if(userDetails != null && StringUtils.isNotBlank(userDetails.getPassword())
&& userDetails.getPassword().equals(password)){
return new UsernamePasswordAuthenticationToken(username,password,authentication.getAuthorities());
}
try {
throw new Exception("RespCodeEnum.NAME_OR_PASSWORD_ERROR");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
5.拦截器实现。
(1)CustomFilter
java
package com.x.x.x.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* OncePerRequestFilter 是 Spring Security 提供的一个抽象类,确保在每个请求中只执行一次特定的过滤逻辑。
* 它是实现自定义过滤器的基础,通常用于对请求进行预处理或后处理。(实现 JWT 会用到这个接口)
* 提供了一种机制,以确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景。
* 通过继承该类,可以轻松实现自定义过滤器适合用于记录日志、身份验证、权限检查等场景。
*
* 本处继承 OncePerRequestFilter 类,并重写 doFilterInternal 方法。
* 但是需要再spring security配置类中注册自定义的过滤器
*/
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 自定义过滤逻辑,例如记录请求日志
System.out.println("Request URI: " + request.getRequestURI());
// 继续执行过滤链
filterChain.doFilter(request, response);
}
}
(2)JwtAuthenticationTokenFilter
java
package com.x.x.x.filter;
import com.x.x.x.dao.BaseUsersDao;
import io.jsonwebtoken.Claims;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.x.x.x.until.JwtUtil;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
/**
* 用于验证账号密码,本处于数据库交互
*/
@Autowired
private BaseUsersDao baseUsersDao;
@Autowired
@Qualifier("userDetailsServiceImpl")//需要指定注入的是那个类,避免报错。
private UserDetailsService userDetailsService;
/**
* 重写了 OncePerRequestFilter 类中的抽象方法 doFilterInternal。
* OncePerRequestFilter 是 Spring Security 提供的一个基础类
* ,设计用来确保过滤器在同一个请求中只执行一次。
* @param request
* @param response
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response
, @NotNull FilterChain filterChain) throws ServletException, IOException {
// 获取请求头的验证信息,即前端传回的token
String token = request.getHeader("Authorization");
System.out.println("----》 JwtAuthenticationTokenFilter,验证token过滤器,获取到的token值:"+token);
//为空时候继续下一步过滤链,即进行登录认证。后续进行格式验证,如果以bearer开始去掉前面的前缀
if (!StringUtils.hasText(token) ) {
System.out.println("----》 JwtAuthenticationTokenFilter,token验证:"+"token为空!");
filterChain.doFilter(request, response);
return;
}
if (token.startsWith("Bearer ")) {
System.out.println("----》 JwtAuthenticationTokenFilter,token格式验证中:"+"token格式以Bearer开头,去掉开头!");
token = token.substring(7);
}
//验证token是否过期
boolean isValid = JwtUtil.validateJwtToken(token);//只在util中只验证是否过期了。
if (!isValid) {
System.out.println("----》 token验证失败,token过期。");
response(response, "验证失败");
return;
}
//获取token载荷中的用户信息
Claims claims = JwtUtil.parseClaim(token).getPayload();
String userid = claims.get("username").toString();
//查询数据库中用户信息
System.out.println("----》 数据库验证用户信息。"+"userid:"+userid);
UserDetails userDetails = userDetailsService.loadUserByUsername(userid);
System.out.println("----》 数据库中数据:"+userDetails.getUsername()+","+userDetails.getPassword());
//设置安全上下文
//创建一个自定义的 UserDetailsImpl 对象,将查询到的用户信息封装。
//创建一个 UsernamePasswordAuthenticationToken 对象,表示用户的认证信息
// ,并将其设置到 Spring Security 的 SecurityContextHolder 中,以便后续请求能够访问到用户的认证信息。
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// 如果是有效的jwt,那么设置该用户为认证后的用户
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//继续过滤链
System.out.println("----》 jwt过滤器执行完毕!"+authenticationToken);
filterChain.doFilter(request, response);
}
private void response(@NotNull HttpServletResponse response,String error) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定义状态码
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\n" +
" \"states\": \""+error+"\",\n" +
" \"message\": \"无效token!\"\n" +
"}");
}
}
6.jwt实现
java
package com.x.x.x.until;
import com.x.x.x.enums.BaseInfoEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.*;
// @Component将这个类标记为 Spring 组件,允许 Spring 管理该类的生命周期,便于依赖注入。
@Component
public class JwtUtil {
/**
* 过期时间(单位:秒),4小时为14400s
*/
public static final int ACCESS_EXPIRE = Integer.parseInt(BaseInfoEnum.fiedIdOf("access_expire").getFiedIdInfo());//14400;
/**
* 加密算法
*/
private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;
/**
* 私钥 / 生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取,切记这个秘钥不能外露,只在服务端使用,在任何场景都不应该流露出去。
* 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
* 应该大于等于 256位(长度32及以上的字符串),并且是随机的字符串
*/
private final static String SECRET = BaseInfoEnum.fiedIdOf("secret").getFiedIdInfo();//"Cpj2cc09BRTstcISP5HtEAMxwuFEh-nJiL1mppdsz8k@lzgs";
/**
* 秘钥实例,相比secretkeyspec方法base64编码指定验证方式,该种方式更加简便安全。
*/
public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());
/**
* jwt签发者
*/
private final static String JWT_ISS = BaseInfoEnum.fiedIdOf("jwt_iss").getFiedIdInfo();
/**
* jwt主题
*/
private final static String SUBJECT = "Peripherals";
/**
* jwt构建器,生成token
* 这些是一组预定义的声明,它们 不是强制性的,而是推荐的 ,以 提供一组有用的、可互操作的声明 。
* iss: jwt签发者
* sub: jwt所面向的用户
* aud: 接收jwt的一方
* exp: jwt的过期时间,这个过期时间必须要大于签发时间
* nbf: 定义在什么时间之前,该jwt都是不可用的.
* iat: jwt的签发时间
* jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
*/
public static String genAccessToken(String username ,String roleId,String company) {
// 令牌id
String uuid = UUID.randomUUID().toString();
Date exprireDate = Date.from(Instant.now().plusSeconds(ACCESS_EXPIRE));
//System.out.println("key:"+KEY);
return Jwts.builder()
// 设置头部信息header
.header()
.add("typ", "JWT")
.add("alg", "HS256")
.and()
// 设置自定义负载信息payload
.claim("username", username )//
.claim("roleId",roleId )
.claim("company",company )
// 令牌ID
.id(uuid)
// 过期日期
.expiration(exprireDate)
// 签发时间
.issuedAt(new Date())
// 主题
.subject(SUBJECT)
// 签发者
.issuer(JWT_ISS)
// 签名
.signWith(KEY, ALGORITHM)
.compact();
}
/**
* 解析token
* @param token token
* @return Jws<Claims>
*/
public static Jws<Claims> parseClaim(String token) {
return Jwts.parser()
.verifyWith(KEY)
.build()
.parseSignedClaims(token);
}
/**
* 获取头部信息
* @param token
* @return
*/
public static JwsHeader parseHeader(String token) {
return parseClaim(token).getHeader();
}
/**
* 获取载荷信息
* @param token
* @return
*/
public static Claims parsePayload(String token) {
return parseClaim(token).getPayload();
}
/**
* token验证,token是否过期正确
* @param token
* @return
*/
public static boolean validateJwtToken(String token) {
try {
// 解析 Token,验证签名。验证载荷
Claims claims = parseClaim(token).getPayload();
//System.out.println("content:---"+claims.get("username"));
// 验证声明(例如过期时间)
if (claims.getExpiration().before(new Date())) {
System.out.println("Token has expired.");
return false;
}
// 在这里可以进行其他自定义验证
// 例如检查用户角色、权限等
// Token 验证通过
return true;
} catch (Exception e) {
// 验证失败
System.out.println("Token validation failed: " + e.getMessage());
return false;
}
}
/**
* 直接获取到载荷的具体内容
* @param token
* @return
*/
public static Map<String, Object> token2userInfo(String token){
Map<String, Object> tokenMap = new HashMap<String, Object>();
Claims claims = parseClaim(token).getPayload();
tokenMap.put("company", claims.get("company"));
tokenMap.put("loginName", claims.get("username"));
tokenMap.put("roleId", claims.get("roleId"));
return tokenMap;
}
//测试
public static void main(String[] args){
String token = genAccessToken("123","admin","123");
System.out.println("token:"+token);
boolean isValid = validateJwtToken(token);
System.out.println(isValid);
System.out.println(parseHeader(token));
System.out.println(parsePayload(token));
}
}
7.接口实现
java
/**
* 用户登录接口。
* 本处调用spring security验证功能。(但本项目是前后端分离的,禁用了security登录页功能,
* 因为其重定向默认只能用"GET"方式请求)
* @param request
* @return
* @throws Exception
*/
@PostMapping("/login")
public Map<String, Object> login(HttpServletRequest request) throws Exception{
Map<String, Object> modelMap = new HashMap<String, Object>();
request.setCharacterEncoding("UTF8");//设置request获取数据的编码方式为utf-8
String loginName = HttpServletRequestUtil.getString(request, "loginName");
String password = HttpServletRequestUtil.getString(request, "password");
if (loginName ==null || loginName.isBlank() || password == null || password.isBlank()){
modelMap.put("success", false);
modelMap.put("msg", "用户名和密码均不能为空");
logger.error("----> 登录失败,用户名和密码为空!");
return modelMap;
}
//认证设置,在后续的方法中,已经设置了连接数据库认证loadUserByUsername
//先设置认证authentication 这一步Authenticated=false
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);
//自动调用loadUserByUsername验证用户名和密码,从数据库中对比查找,如果找到了会返回一个带有认证的封装后的用户,否则会报错,自动处理。(这里我们假设我们配置的security是基于数据库查找的)
try{
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authenticate);
String token = genAccessToken(loginName,"admin","123");
modelMap.put("token",token);
modelMap.put("success", true);
return modelMap;
} catch (Exception e) {
modelMap.put("success", false);
modelMap.put("msg", "用户名或密码错误");
logger.error("----> 登录失败,用户名或密码错误!");
return modelMap;
}
}
这里需要注意:
1.一般是url请求带token,直接验证token,通过则授权,在过滤器JwtAuthenticationTokenFilter中UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 验证结果是true的。
2.不带token则在控制器中对用户密码进行验证,因为在loadUserByUsername方法中设置了对用户名密码的验证,所以使用UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);后,需要手动使用 Authentication authenticate = authenticationManager.authenticate(authenticationToken);进行验证,验证通过则验证结果是true的。
![](https://i-blog.csdnimg.cn/direct/034f147b66a442e69c49d0731fe8d54f.png)