在Spring Boot应用程序中配置安全性
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Spring Boot应用程序中配置安全性相关的知识,希望对你有一定的参考价值。
我正在将应用程序升级到Spring Boot 2.0.3
。
但我的登录请求是未经授权的:
curl -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/users/login" -X POST -d "{ "email" : "myemail@somedomain.com", "password" : "xxxxx" }" -i
回应是401 Unauthorized access. You failed to authenticate
。
它由我的自定义入口点给出:
@Component
public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private static Logger logger = LoggerFactory.getLogger(RESTAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
logger.debug("Security - RESTAuthenticationEntryPoint - Entry point 401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized access. You failed to authenticate.");
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("User REST");
super.afterPropertiesSet();
}
}
调试器显示我的authenticate
的CustomAuthenticationProvider
方法未被调用,因为我预期它是:
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
CredentialsService credentialsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String email = authentication.getName();
String password = authentication.getCredentials().toString();
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
User user = null;
try {
user = credentialsService.findByEmail(new EmailAddress(email));
} catch (IllegalArgumentException e) {
throw new BadCredentialsException("The login " + email + " and password could not match.");
}
if (user != null) {
if (credentialsService.checkPassword(user, password)) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new UsernamePasswordAuthenticationToken(email, password, grantedAuthorities);
} else {
throw new BadCredentialsException("The login " + user.getEmail() + " and password could not match.");
}
}
throw new BadCredentialsException("The login " + authentication.getPrincipal() + " and password could not match.");
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
但是运行了过滤器并找到了一个空令牌:
@Component
public class AuthenticationFromTokenFilter extends OncePerRequestFilter {
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
tokenAuthenticationService.authenticateFromToken(request);
chain.doFilter(request, response);
}
}
@Service
public class TokenAuthenticationServiceImpl implements TokenAuthenticationService {
private static Logger logger = LoggerFactory.getLogger(TokenAuthenticationServiceImpl.class);
private static final long ONE_WEEK = 1000 * 60 * 60 * 24 * 7;
private static final String TOKEN_URL_PARAM_NAME = "token";
@Autowired
private ApplicationProperties applicationProperties;
@Autowired
private UserDetailsService userDetailsService;
public void addTokenToResponseHeader(HttpHeaders headers, String username) {
String token = buildToken(username);
headers.add(CommonConstants.AUTH_HEADER_NAME, token);
}
public void addTokenToResponseHeader(HttpServletResponse response, Authentication authentication) {
String username = authentication.getName();
if (username != null) {
String token = buildToken(username);
response.addHeader(CommonConstants.AUTH_HEADER_NAME, token);
}
}
private String buildToken(String username) {
String token = null;
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
Date expirationDate = new Date(System.currentTimeMillis() + ONE_WEEK);
token = CommonConstants.AUTH_BEARER + " " + Jwts.builder().signWith(HS256, getEncodedPrivateKey()).setExpiration(expirationDate).setSubject(userDetails.getUsername()).compact();
}
return token;
}
public Authentication authenticateFromToken(HttpServletRequest request) {
String token = extractAuthTokenFromRequest(request);
logger.debug("The request contained the JWT token: " + token);
if (token != null && !token.isEmpty()) {
try {
String username = Jwts.parser().setSigningKey(getEncodedPrivateKey()).parseClaimsJws(token).getBody().getSubject();
if (username != null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.debug("Security - The filter authenticated fine from the JWT token");
}
} catch (SignatureException e) {
logger.info("The JWT token " + token + " could not be parsed.");
}
}
return null;
}
private String extractAuthTokenFromRequest(HttpServletRequest request) {
String token = null;
String header = request.getHeader(CommonConstants.AUTH_HEADER_NAME);
if (header != null && header.contains(CommonConstants.AUTH_BEARER)) {
int start = (CommonConstants.AUTH_BEARER + " ").length();
if (header.length() > start) {
token = header.substring(start - 1);
}
} else {
// The token may be set as an HTTP parameter in case the client could not set it as an HTTP header
token = request.getParameter(TOKEN_URL_PARAM_NAME);
}
return token;
}
private String getEncodedPrivateKey() {
String privateKey = applicationProperties.getAuthenticationTokenPrivateKey();
return Base64.getEncoder().encodeToString(privateKey.getBytes());
}
}
我的安全配置是:
@Configuration
@EnableWebSecurity
@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.user.rest.security", "com.thalasoft.user.rest.filter" })
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationFromTokenFilter authenticationFromTokenFilter;
@Autowired
private SimpleCORSFilter simpleCORSFilter;
@Autowired
private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(new CustomAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(simpleCORSFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
.headers().cacheControl().disable().frameOptions().disable()
.and()
.userDetailsService(userDetailsService)
.authorizeRequests()
.antMatchers(RESTConstants.SLASH + UserDomainConstants.USERS + RESTConstants.SLASH + UserDomainConstants.LOGIN).permitAll()
.antMatchers(RESTConstants.SLASH + RESTConstants.ERROR).permitAll()
.antMatchers("/**").hasRole(UserDomainConstants.ROLE_ADMIN).anyRequest().authenticated();
}
}
用户详细信息服务是:
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private CredentialsService credentialsService;
@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username != null && !username.isEmpty()) {
User user = credentialsService.findByEmail(new EmailAddress(username));
if (user != null) {
return new UserDetailsWrapper(user);
}
}
throw new UsernameNotFoundException("The user " + username + " was not found.");
}
}
为什么自定义身份验证提供程序不验证用户名和密码?
更新:我在这个guide读了一些有趣和令人费解的东西
Note that the AuthenticationManagerBuilder is @Autowired into a method in a @Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way (using an @Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one. In a Spring Boot application you can @Autowired the global one into another bean, but you can’t do that with the local one unless you explicitly expose it yourself.
那么,我使用configure
方法设置authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
有什么问题吗?我尝试了以下配置,而不是上面的配置:
@Autowired
public void initialize(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}
但它仍然没有根据请求运行自定义身份验证提供程序。
我也试过过滤器,如下所示:
http.addFilterAfter(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class);
而不是addFilterBefore
但它没有改变任何问题。
根据我的说法,当我们通过实现GenericFilterBean实现我们自己的ApplicationFilter时,我们需要检查从请求接收的令牌是否有效。如果它无效,那么我们需要将令牌转储到安全上下文中(以便身份验证提供程序获取)。我没有通过你的过滤器类。但这对我有用:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httprequset=(HttpServletRequest)request;
String uname=request.getParameter("username");
String pwd=request.getParameter("password");
String role=request.getParameter("role");
List<GrantedAuthority> l = new ArrayList<>();
l.add( new SimpleGrantedAuthority(role.toUpperCase()) );
UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(uname,pwd,l);
token.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(httprequset, response);
}
在WebSecurityConfiguration里面配置(HttpSecurity http)方法:
http.authorizeRequests().antMatchers("/api/users/login").permitAll();
http.authorizeRequests().anyRequest().authenticated();
添加相同的顺序。
说明:应允许登录和注销请求,无需任何身份验证
有效的示例配置方法是:
http.formLogin().disable().logout().disable().httpBasic().disable();
http.authorizeRequests().antMatchers("/logout", "/login", "/").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(new SomeFilter(), SecurityContextHolderAwareRequestFilter.class);
http.addFilterBefore(new CORSFilter(env), ChannelProcessingFilter.class);
http.addFilterBefore(new XSSFilter(),CORSFilter.class);
以上是关于在Spring Boot应用程序中配置安全性的主要内容,如果未能解决你的问题,请参考以下文章