在spring security登录中增加额外的用户需求并处理各种异常
Posted
技术标签:
【中文标题】在spring security登录中增加额外的用户需求并处理各种异常【英文标题】:Add additional user requirements in spring security login and handle various exceptions 【发布时间】:2021-11-04 18:48:28 【问题描述】:我是 Spring 安全的新手,我已经使用 JWT 为我的应用程序实现了基本的用户登录功能。除了在登录时检查用户名和密码之外,我还想添加其他参数,例如“帐户已验证”布尔条件,但我不确定在哪里添加此要求。此外,如果“帐户已验证”条件为假,我需要返回 403 禁止响应状态消息,如果根本找不到用户名密码组合,则返回不同的响应状态消息。这是我目前拥有的代码,它正确处理现有用户的登录(不检查“帐户已验证”条件),并在找到用户时始终抛出 401。任何反馈都会有所帮助。
WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
private final ApplicationUserDetailsService applicationUserDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(ApplicationUserDetailsService userDetailsService)
this.applicationUserDetailsService = userDetailsService;
this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
httpSecurity
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
@Bean
public PasswordEncoder encoder()
return this.bCryptPasswordEncoder;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(applicationUserDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
用户详细信息服务
public class ApplicationUserDetailsService implements UserDetailsService
private final ApplicationUserRepository applicationUserRepository;
public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository)
this.applicationUserRepository = applicationUserRepository;
@Override
public UserDetails loadUserByUsername(String nickname)
throws UsernameNotFoundException, UserIsNotActiveException
Optional<ApplicationUser> applicationUser =
applicationUserRepository.findByNickname(nickname);
if (!applicationUser.isPresent())
throw new UsernameNotFoundException(nickname);
return new User(
applicationUser.get().getNickname(),
applicationUser.get().getPassword(),
emptyList());
身份验证过滤器
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter
private AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager)
this.authenticationManager = authenticationManager;
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException
try
ApplicationUser applicationUser =
new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
applicationUser.getNickname(),
applicationUser.getPassword(),
new ArrayList<>()));
catch (IOException e)
throw new RuntimeException(e);
@Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth)
Date exp = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
Key key = Keys.hmacShaKeyFor(KEY.getBytes());
Claims claims = Jwts.claims().setSubject(((User) auth.getPrincipal()).getUsername());
String token =
Jwts.builder()
.setClaims(claims)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(exp)
.compact();
res.addHeader("token", token);
授权过滤器
public AuthorizationFilter(AuthenticationManager authManager)
super(authManager);
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException
String header = request.getHeader(HEADER_NAME);
if (header == null)
chain.doFilter(request, response);
return;
UsernamePasswordAuthenticationToken authentication = authenticate(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
private UsernamePasswordAuthenticationToken authenticate(HttpServletRequest request)
String token = request.getHeader(HEADER_NAME);
if (token != null)
Jws<Claims> user =
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(KEY.getBytes()))
.build()
.parseClaimsJws(token);
if (user != null)
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
else
return null;
return null;
应用用户
public class ApplicationUser
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
@Column(unique = true)
String email;
@Column(unique = true)
String nickname;
String biography;
String password; // Hashed
@Builder.Default boolean isActive = false;
【问题讨论】:
【参考方案1】:接口UserDetails
(由UserDetailsService
返回)有一些实用方法可以帮助您。
在帐户未激活时,您可以通过UserDetails#isEnabled
方法返回false
,或者您也可以使用UserDetails#isAccountNonLocked
。
这些方法将在 AbstractUserDetailsAuthenticationProvider$Default(Pre/Post)AuthenticationChecks
类上自动验证。
用户完成激活流程后,您可以将属性更改为true
,它将允许用户进行身份验证。
提示:将logging.level.org.springframework.security=TRACE
添加到您的application.properties
以帮助调试。
【讨论】:
以上是关于在spring security登录中增加额外的用户需求并处理各种异常的主要内容,如果未能解决你的问题,请参考以下文章
相同 API 的 Spring Security Basic Auth 和 Form 登录