您需要做的就是创建一个自定义安全过滤器,并在spring security BasicAuthenticationFilter之前插入此过滤器。示例代码-
public class CustomAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(final HttpServletRequest request,final HttpServletResponse response,final FilterChain filterChain) throws ServletException,IOException {
String authHeader = request.getHeaders("Authorization");
//Decode the authHeader
//Validate the authHeader with your username & password
if(invalid) {
//throw exception and abort processing
}
filterChain.doFilter(request,response);
}
}
现在,您可以创建bean或将其命名为@component
,以便spring可以为您选择并创建bean。
在您的安全配置中,添加以下内容-
@Configuration
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new CustomAuthenticationFilter(),BasicAuthenticationFilter.class);
}
}
,
好像您正在使用REST API一样,您可以使用类似于(https://medium.com/@hantsy/protect-rest-apis-with-spring-security-and-jwt-5fbc90305cc5)的JWT和自定义过滤器
,
我在一个URL的标题中发送一个身份验证令牌。我需要
从URL获取此身份验证令牌并进行解码。如果用户名和
密码匹配...
通常,使用令牌进行身份验证的目的是摆脱用户名和密码检查。
Spring Security支持的开箱即用的基本HTTP身份验证假定在HTTP标头中传递base64编码的用户名和密码: Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
(base64编码的Aladdin:OpenSesame
)。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
如果仍然需要以其他方式从令牌中提取用户名和密码,请考虑以下示例。
考虑到您具有以下REST控制器:
@RestController
public class TestRestController {
@GetMapping("/api/getStudent/v1")
public String helloWorld() {
return "Hello,World!";
}
@GetMapping("/info")
public String test() {
return "Test";
}
}
为了使端点/api/getStudent/v1
受保护和/info
公开,并从HTTP请求标头中提取主体和凭证,您需要实现自定义AbstractAuthenticationProcessingFilter
:
public class HeaderUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public HeaderUsernamePasswordAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationSuccessHandler((request,response,authentication) -> {
});
setAuthenticationFailureHandler((request,exception) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,exception.getMessage()));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException,IOException,ServletException {
String token = request.getHeader("token");
String username = token; //get username from token
String password = token; //get password from token
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username,password);
return getAuthenticationManager().authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication authResult) throws IOException,ServletException {
super.successfulAuthentication(request,chain,authResult);
chain.doFilter(request,response);
}
}
此过滤器必须从传入的标头中提取令牌的主体和凭证,然后尝试通过Spring Security进行认证。
接下来,您必须创建此自定义过滤器的实例,并将Spring Security配置为在安全过滤器链(.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class)
)中添加该过滤器:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public HeaderUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
HeaderUsernamePasswordAuthenticationFilter authenticationFilter =
new HeaderUsernamePasswordAuthenticationFilter(new AntPathRequestMatcher("/api/**"));
authenticationFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(
authenticationFilter(),UsernamePasswordAuthenticationFilter.class);
}
//...
}
让过滤器了解Spring Security authenticationManagerBean
:authenticationFilter.setAuthenticationManager(authenticationManagerBean());
很重要。
您可以通过传递RequestMatcher
来配置要保护的端点,例如: new AntPathRequestMatcher("/api/**")
。
对于测试,您可以创建内存UserDetailsService
,并使用用户名test
,密码test
和权限admin
测试用户:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("test")
.password(passwordEncoder().encode("test"))
.authorities("admin");
}
}
运行该应用程序,然后尝试在不进行身份验证的情况下访问公共端点:
curl -i http://localhost:8080/info
HTTP/1.1 200
Test
未经身份验证的受保护端点:
curl -i http://localhost:8080/api/getStudent/v1
HTTP/1.1 401
没有无效令牌的受保护端点:
curl -i http://localhost:8080/api/getStudent/v1 -H 'token: not_valid'
HTTP/1.1 401
最后是具有有效令牌的受保护端点:
curl -i http://localhost:8080/api/getStudent/v1 -H 'token: test'
HTTP/1.1 200
Hello,World!
,
您可以尝试以下方法。我在这里使用了JWT身份验证。根据您的问题,您可以使用spring的@Preauthorize注释对端点“ api / getStudent / v1”进行预授权。
以下是将引导用户登录的终点。
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginForm loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(),loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.generateJwtToken(authentication);
UserPrinciple userPrinciple = (UserPrinciple) authentication.getPrincipal();
String name = userRepo.findById(userPrinciple.getId()).get().getName();
return ResponseEntity.ok(new JwtResponse(jwt,userPrinciple.getUsername(),userPrinciple.getAuthorities(),name,userPrinciple.getGender()));
}
以下是 WebSecurityConfig 类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthEntryPoint unauthorizedHandler;
@Bean
public JwtAuthTokenFilter authenticationJwtTokenFilter() {
return new JwtAuthTokenFilter();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> customAuthorizationRequestRepository() {
return new HttpSessionOAuth2AuthorizationRequestRepository();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationJwtTokenFilter(),UsernamePasswordAuthenticationFilter.class);
}
}
在 JWTProvider 类之后,包括生成JWT令牌的方法。(注意:我已将每个用户的电子邮件设置为用户名。您可以根据自己的意愿进行操作)
@Component
public class JwtProvider {
@Autowired
UserRepository userRepo;
private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);
public String generateJwtToken(Authentication authentication) {
UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();
String name = userRepo.findById(userPrincipal.getId()).get().getName();
return Jwts.builder()
.setSubject((userPrincipal.getUsername())) //getUsername returns the email
.claim("id",userPrincipal.getId() )
.claim("name",name)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512,SECRET)
.compact();
}
public String generateJwtToken(UserPrinciple userPrincipal) {
String name = userRepo.findById(userPrincipal.getId()).get().getName();
return Jwts.builder()
.setSubject((userPrincipal.getUsername())) //getUsername returns the email
.claim("id",userPrincipal.getId() )
.claim("name",name)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512,SECRET)
.compact();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature -> Message: {} ",e);
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token -> Message: {}",e);
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token -> Message: {}",e);
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token -> Message: {}",e);
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty -> Message: {}",e);
}
return false;
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody().getSubject();
}
}
以下是在WebSecurityConfig类中启动的 JWTAuthTokenFilter 类。它在这里从rquest解码令牌并检查令牌是否有效
public class JwtAuthTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtProvider tokenProvider;
@Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,FilterChain filterChain)
throws ServletException,IOException {
try {
String jwt = getJwt(request);
if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
String email = tokenProvider.getUserNameFromJwtToken(jwt);//returns the email instead of username
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails,null,userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Can NOT set user authentication -> Message: {}",e);
}
filterChain.doFilter(request,response);
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.replace("Bearer ","");
}
return null;
}
}
以下是 JWTAuthEntryPoint 。检查 WebSecurityConfig 类以了解此类的使用
@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
@Override
public void commence(HttpServletRequest request,AuthenticationException e)
throws IOException,ServletException {
logger.error("Unauthorized error. Message - {}",e.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Error -> Unauthorized");
}
}
以下是我为约束创建的类
public class SecurityConstraints {
public static final String SECRET = "********";//add any secret you want
public static final long EXPIRATION_TIME = 864_000_000L;
}