如何为这种情况配置 HttpSecurity (Spring Boot)
How to configure HttpSecurity for this situation (Spring Boot)
条件:
- 未经身份验证的用户从 /oauth/token
请求令牌
- 未经身份验证的用户也可以在 /swagger-ui.html
访问 swagger 文档
- 所有其他端点都应受到保护,即需要有效令牌才能使用。
我尝试过的:
SecurityConfig.java - 可能是问题的根源
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/oauth/token")
.addFilterBefore(new RESTAuthenticationTokenProcessingFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
CustomAuthenticationProvider.java
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
final
UserService userService;
final
TokenService tokenService;
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
@Autowired
public CustomAuthenticationProvider(TokenService tokenService, UserService userService) {
this.tokenService = tokenService;
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
Object credentials = authentication.getCredentials();
if (!(credentials instanceof String)) return null;
String password = credentials.toString();
// TODO implement hashing and salting of passwords
UserDetails user = userService.loadUserByUsername(username);
if (!user.getPassword().equals(password)) throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
TokenModel tokenModel = tokenService.allocateToken(user.getUsername());
authentication.setAuthenticated(true);
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
return TokenRequestModel.class.isAssignableFrom(authentication);
}
}
RESTAuthenticationTokenProcessingFilter.java
@Component
public class RESTAuthenticationTokenProcessingFilter extends GenericFilterBean {
public RESTAuthenticationTokenProcessingFilter() {
}
public RESTAuthenticationTokenProcessingFilter(UserService userService, String restUser) {
this.userService = userService;
this.REST_USER = restUser;
}
@Autowired
private TokenService tokenService;
private UserService userService;
private String REST_USER;
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = getAsHttpRequest(request);
String authToken = extractAuthTokenFromRequest(httpRequest);
if (authToken == null) throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
String[] parts = authToken.split(" ");
if (parts.length == 2) {
String tokenKey = parts[1];
if (validateTokenKey(tokenKey)) {
TokenModel token = tokenService.getTokenById(tokenKey);
//List<String> allowedIPs = new Gson().fromJson(token.getAllowedIP(), new TypeToken<ArrayList<String>>() {}.getType());
//if (isAllowIP(allowedIPs, request.getRemoteAddr())) {
if (token != null) {
if (token.getExpires_in() > 0) {
UserDetails userDetails = userService.loadUserByUsername(REST_USER);
TokenRequestModel authentication = new TokenRequestModel(null, null, userDetails.getUsername(), userDetails.getPassword());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("Authenticated " + token.getAccess_token() + " via IP: " + request.getRemoteAddr());
} else {
log.info("Unable to authenticate the token: " + authToken + ". Incorrect secret or token is expired");
throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
}
//} else {
//log.info("Unable to authenticate the token: " + authToken + ". IP - " + request.getRemoteAddr() + " is not allowed");l
}
}
} else {
log.info("Unable to authenticate the token: " + authToken + ". Key is broken");
throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
}
chain.doFilter(request, response);
}
private boolean validateTokenKey(String tokenKey) {
String[] parts = tokenKey.split("-");
return parts.length == 5;
}
private HttpServletRequest getAsHttpRequest(ServletRequest request) {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("Expecting an HTTP request");
}
return (HttpServletRequest) request;
}
private String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
// Get token from header
String authToken = httpRequest.getHeader("authorisation");
// If token not found get it from request parameter
if (authToken == null) {
authToken = httpRequest.getParameter("access_token");
}
return authToken;
}
}
这是任何用户发出请求的结果 "Authorisation" header 能够访问所有资源。没有授权的用户 header 无法请求使用令牌。
我花了很多时间尝试借鉴其他示例,并通读了 HTTPSecurity class 的文档及其相关的 classes,但我无法理解这是怎么回事配置即可实现。
如有任何帮助,我们将不胜感激!
编辑 --
由于项目的性质,我不得不遵循一个涉及略微简化版本的 oauth2 的协议。不幸的是,这意味着要实施 Spring Security 中已经提供的很多内容(即我无法使用 Spring-Security-oauth2 或 Spring Security 5)。以满足我的特定需求。如果您有任何建议,我将不胜感激。
我从 tutorial 中找到了一个解决方案,该解决方案涉及实现我自己的整个授权过程版本。但它有效!
我的SecurityConfig.javaclass变成了:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationSuccessHandler loginSuccessfulHandler;
private final AuthenticationFailureHandler loginFailureHandler;
private final AccessDeniedHandler customAccessDeniedHandler;
private final AuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
public SecurityConfig(AuthenticationSuccessHandler loginSuccessfulHandler, AuthenticationFailureHandler loginFailureHandler, AccessDeniedHandler customAccessDeniedHandler, AuthenticationEntryPoint customAuthenticationEntryPoint) {
this.loginSuccessfulHandler = loginSuccessfulHandler;
this.loginFailureHandler = loginFailureHandler;
this.customAccessDeniedHandler = customAccessDeniedHandler;
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("password").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // disable CSRF for this application
.formLogin() // Using form based login instead of Basic Authentication
.loginProcessingUrl("/oauth/token") // Endpoint which will process the authentication request. This is where we will post our credentials to authenticate
.successHandler(loginSuccessfulHandler)
.failureHandler(loginFailureHandler)
.and()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll() // Enabling URL to be accessed by all users (even un-authenticated)
.antMatchers("/swagger-ui.html").permitAll()
//.antMatchers("/secure/admin").access("hasRole('ADMIN')") // Configures specified URL to be accessed with user having role as ADMIN
.anyRequest().authenticated() // Any resources not mentioned above needs to be authenticated
.and()
.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.anonymous().disable(); // Disables anonymous authentication with anonymous role.
}
所以我必须通过实现现有接口来实现我的登录(success/failure)处理程序等。现在我只需要实现我自己的 AuthenticationMangager 而不是当前正在使用的内存配置。
@dur 感谢您的帮助! :)
条件:
- 未经身份验证的用户从 /oauth/token 请求令牌
- 未经身份验证的用户也可以在 /swagger-ui.html 访问 swagger 文档
- 所有其他端点都应受到保护,即需要有效令牌才能使用。
我尝试过的:
SecurityConfig.java - 可能是问题的根源
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/oauth/token")
.addFilterBefore(new RESTAuthenticationTokenProcessingFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
CustomAuthenticationProvider.java
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
final
UserService userService;
final
TokenService tokenService;
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
@Autowired
public CustomAuthenticationProvider(TokenService tokenService, UserService userService) {
this.tokenService = tokenService;
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
Object credentials = authentication.getCredentials();
if (!(credentials instanceof String)) return null;
String password = credentials.toString();
// TODO implement hashing and salting of passwords
UserDetails user = userService.loadUserByUsername(username);
if (!user.getPassword().equals(password)) throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
TokenModel tokenModel = tokenService.allocateToken(user.getUsername());
authentication.setAuthenticated(true);
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
return TokenRequestModel.class.isAssignableFrom(authentication);
}
}
RESTAuthenticationTokenProcessingFilter.java
@Component
public class RESTAuthenticationTokenProcessingFilter extends GenericFilterBean {
public RESTAuthenticationTokenProcessingFilter() {
}
public RESTAuthenticationTokenProcessingFilter(UserService userService, String restUser) {
this.userService = userService;
this.REST_USER = restUser;
}
@Autowired
private TokenService tokenService;
private UserService userService;
private String REST_USER;
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = getAsHttpRequest(request);
String authToken = extractAuthTokenFromRequest(httpRequest);
if (authToken == null) throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
String[] parts = authToken.split(" ");
if (parts.length == 2) {
String tokenKey = parts[1];
if (validateTokenKey(tokenKey)) {
TokenModel token = tokenService.getTokenById(tokenKey);
//List<String> allowedIPs = new Gson().fromJson(token.getAllowedIP(), new TypeToken<ArrayList<String>>() {}.getType());
//if (isAllowIP(allowedIPs, request.getRemoteAddr())) {
if (token != null) {
if (token.getExpires_in() > 0) {
UserDetails userDetails = userService.loadUserByUsername(REST_USER);
TokenRequestModel authentication = new TokenRequestModel(null, null, userDetails.getUsername(), userDetails.getPassword());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("Authenticated " + token.getAccess_token() + " via IP: " + request.getRemoteAddr());
} else {
log.info("Unable to authenticate the token: " + authToken + ". Incorrect secret or token is expired");
throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
}
//} else {
//log.info("Unable to authenticate the token: " + authToken + ". IP - " + request.getRemoteAddr() + " is not allowed");l
}
}
} else {
log.info("Unable to authenticate the token: " + authToken + ". Key is broken");
throw new NotAuthorisedException(AuthorisationFailureTypes.INVALID_REQUEST);
}
chain.doFilter(request, response);
}
private boolean validateTokenKey(String tokenKey) {
String[] parts = tokenKey.split("-");
return parts.length == 5;
}
private HttpServletRequest getAsHttpRequest(ServletRequest request) {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("Expecting an HTTP request");
}
return (HttpServletRequest) request;
}
private String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
// Get token from header
String authToken = httpRequest.getHeader("authorisation");
// If token not found get it from request parameter
if (authToken == null) {
authToken = httpRequest.getParameter("access_token");
}
return authToken;
}
}
这是任何用户发出请求的结果 "Authorisation" header 能够访问所有资源。没有授权的用户 header 无法请求使用令牌。
我花了很多时间尝试借鉴其他示例,并通读了 HTTPSecurity class 的文档及其相关的 classes,但我无法理解这是怎么回事配置即可实现。
如有任何帮助,我们将不胜感激!
编辑 --
由于项目的性质,我不得不遵循一个涉及略微简化版本的 oauth2 的协议。不幸的是,这意味着要实施 Spring Security 中已经提供的很多内容(即我无法使用 Spring-Security-oauth2 或 Spring Security 5)。以满足我的特定需求。如果您有任何建议,我将不胜感激。
我从 tutorial 中找到了一个解决方案,该解决方案涉及实现我自己的整个授权过程版本。但它有效!
我的SecurityConfig.javaclass变成了:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationSuccessHandler loginSuccessfulHandler;
private final AuthenticationFailureHandler loginFailureHandler;
private final AccessDeniedHandler customAccessDeniedHandler;
private final AuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
public SecurityConfig(AuthenticationSuccessHandler loginSuccessfulHandler, AuthenticationFailureHandler loginFailureHandler, AccessDeniedHandler customAccessDeniedHandler, AuthenticationEntryPoint customAuthenticationEntryPoint) {
this.loginSuccessfulHandler = loginSuccessfulHandler;
this.loginFailureHandler = loginFailureHandler;
this.customAccessDeniedHandler = customAccessDeniedHandler;
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("password").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // disable CSRF for this application
.formLogin() // Using form based login instead of Basic Authentication
.loginProcessingUrl("/oauth/token") // Endpoint which will process the authentication request. This is where we will post our credentials to authenticate
.successHandler(loginSuccessfulHandler)
.failureHandler(loginFailureHandler)
.and()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll() // Enabling URL to be accessed by all users (even un-authenticated)
.antMatchers("/swagger-ui.html").permitAll()
//.antMatchers("/secure/admin").access("hasRole('ADMIN')") // Configures specified URL to be accessed with user having role as ADMIN
.anyRequest().authenticated() // Any resources not mentioned above needs to be authenticated
.and()
.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.anonymous().disable(); // Disables anonymous authentication with anonymous role.
}
所以我必须通过实现现有接口来实现我的登录(success/failure)处理程序等。现在我只需要实现我自己的 AuthenticationMangager 而不是当前正在使用的内存配置。
@dur 感谢您的帮助! :)