Security OAuth2 Single Sign Off - java

I have two clients (client1, client2) and an OAuth (authorization, resource).
I want to logout from one of clients and the other will be logout. I have tried this spring-boot-oauth2-single-sign-off-logout but this only logout my client1 and client2 is still logged in!
Then I try to revoke my tokens while I use this code below:
String username = principal.getName();
Collection<OAuth2AccessToken> accessTokens = tokenStore.findTokensByClientIdAndUserName("client1", username);
accessTokens.forEach(a -> tokenServices.revokeToken(a.getValue()));
This code did not work, even the client1 is still logged in! While I see my redis is empty and there is no token already, but my client1 is still logged in! How that possible?
===========================================================================
Here is my configuration:
Client - application.yml:
server:
port: 8081
servlet:
context-path: /clt1
spring:
application:
name: client1
thymeleaf:
cache: false
security:
oauth2:
client:
client-id: client1
client-secret: secret1
userAuthorizationUri: http://localhost:8000/oa/oauth/authorize
access-token-uri: http://localhost:8000/oa/oauth/token
scope: read, write
#pre-established-redirect-uri: http://localhost:8081/clt1/callback
#registered-redirect-uri: http://localhost:8081/clt1/callback
#use-current-uri: false
resource:
user-info-uri: http://localhost:8000/oa/user
#jwt:
# key-uri: http://localhost:8000/oa/oauth/token_key
logging:
level:
root: info
Client - SecurityConfig:
#Configuration
#EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers().permitAll()
.anyRequest().authenticated()
.and()
.logout().logoutSuccessUrl("http://localhost:8000/oa/revokeClient").permitAll();
}
}
Oauth - application.yml:
server:
port: 8000
servlet:
context-path: /oa
spring:
application:
name: security
redis:
host: 127.0.0.1
port: 6379
thymeleaf:
cache: false
logging:
level:
root: info
Oauth - AuthorizationConfig:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("client1")
.secret(passwordEncoder.encode("secret1"))
.scopes("read", "write")
.redirectUris("http://localhost:8081/clt1/login")
.authorizedGrantTypes("authorization_code", "refresh_token")
.autoApprove(true)
.and()
.withClient("client2")
.secret(passwordEncoder.encode("secret2"))
.scopes("read", "write")
.redirectUris("http://localhost:8082/clt2/login")
.authorizedGrantTypes("authorization_code", "refresh_token")
.autoApprove(true);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore);
}
}
Oauth - ResourceConfig:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests().anyRequest().authenticated();
}
}
Oauth - SecurityConfig:
#Configuration
#EnableWebSecurity
#Order(1)//SecurityConfig >> ResourceConfig
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers()
.antMatchers("/loginPage", "/login**", "/registerPage", "/register", "/oauth/authorize", "/revokeClient")
.and()
.authorizeRequests()
.antMatchers("/registerPage", "/register").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().loginPage("/loginPage").loginProcessingUrl("/login").permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/docs/**", "/fonts/**", "/img/**", "/js/**", "/plugins/**");
}
}
Oauth - Application:
#SpringBootApplication
#Configuration
public class SsoDemoOauthApplication {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
private RedisConnectionFactory connectionFactory;
#Bean
public TokenStore tokenStore() {
return new RedisTokenStore(connectionFactory);
}
public static void main(String[] args) {
SpringApplication.run(SsoDemoOauthApplication.class, args);
}
}

I admit not beeing too clever, but what about putting
.logout().logoutSuccessUrl("http://localhost:8000/oa/logout").permitAll();
instead of
.logout().logoutSuccessUrl("http://localhost:8000/oa/revokeClient").permitAll();
in SecurityConfig of client app? Any drawback?

Related

Spring Security OAuth2 is not working with Bearer Authorization(Rest api request)

I have two microservices, the first for OAuth2 and the second for API. When I log in from the browser, everything works fine, authorization passes and redirection to my API works.
But when I try to do it through Postman(rest), I don’t get access to API.
OAuth2-Server:
Source code for OAuth2 microservice as OAuth2-Server and Resource-Server:
#Configuration
#EnableAuthorizationServer
#EnableResourceServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("SampleClientId")
.secret(passwordEncoder.encode("secret"))
.authorizedGrantTypes("authorization_code", "password", "client_credentials")
.scopes("user_info")
.autoApprove(true)
.accessTokenValiditySeconds(3600)
;
}
}
#Configuration
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
}
#Override
#Bean(name = "userDetailsService")
public UserDetailsService userDetailsServiceBean()
throws Exception {
return super.userDetailsServiceBean();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception { // #formatter:off
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and().csrf().disable();
} // #formatter:on
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("john")
.password(passwordEncoder().encode("123"))
.roles("USER");
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
#RestController
public class UserController {
#RequestMapping("/user/me")
public Principal user(Principal principal) {
System.out.println(principal);
return principal;
}
}
Client-sso:
Source code for Client microservice to use AuthorizationServer for athunticate:
#Configuration
#EnableOAuth2Sso
#EnableWebSecurity
public class UiSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.logout().permitAll()
.and()
.httpBasic().disable();
}
}
#RestController
public class UserController {
#RequestMapping("/test")
#ResponseBody
public String user() {
return "hiii";
}
}
server:
port: 8082
security:
oauth2:
client:
clientId: SampleClientId
clientSecret: secret
accessTokenUri: http://localhost:8080/oauth/token
userAuthorizationUri: http://localhost:8080/oauth/authorize
resource:
userInfoUri: http://localhost:8080/user/me

Config CORS in a client/server system with Spring Boot, Apache and Tomcat

I am developing a client-server architecture for the first time and I have some problems to configure the server to accept CORS.
I've read, searched and test a lot, but I can not make it work in my system, I do not know what is wrong.
I developed the client inAngular and the web service in Spring Boot 2.0.4 with Oauth2 security. On the server there are running an Apache that only accepts requests from port 443 to serve the web and redirect requests through port 8443 to the web service deployed in Tomcat 8.5 that is listening on port 8081.
<VirtualHost _default_:8443>
ProxyPass / http://localhost:8081/
ProxyPassReverse / http://localhost:8081/
DocumentRoot /var/www/html
...
Changes that I made in the Apache configuration
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "authorization"
</IfModule>
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ClientDetailsService clientDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
//#f:off
http.cors()
.and()
.csrf()
.disable()
.anonymous()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
//#f:on
}
#Override
public void configure(WebSecurity web) throws Exception
{
super.configure(web);
web.ignoring()
.antMatchers("/v1/user/save")
.antMatchers("/v1/user/existsEMail")
.antMatchers("/v1/userAccess/existsUsername");
web.ignoring()
.antMatchers(HttpMethod.OPTIONS,"/**");
}
#Bean
public TokenStore tokenStore()
{
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore)
{
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore)
{
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
#Bean
public BCryptPasswordEncoder encoder()
{
BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom();
SecureRandom random = new SecureRandom(keyGenerator.generateKey());
return new BCryptPasswordEncoder(10, random);
}
#Bean
CorsConfigurationSource corsConfigurationSource()
{
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.HEAD.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name()));
config.setAllowCredentials(true);
config.combine(config.applyPermitDefaultValues());
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter
{
#Autowired
private TokenStore tokenStore;
#Autowired
private UserAccessService userDetailsService;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception
{
configurer.inMemory()
.withClient(SecurityConstant.CLIENT_ID)
.secret(SecurityConstant.CLIENT_SECRET)
.accessTokenValiditySeconds(SecurityConstant.ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(SecurityConstant.REFRESH_TOKEN_VALIDITY_SECONDS)
.scopes(SecurityConstant.SCOPE_READ, SecurityConstant.SCOPE_WRITE)
.authorizedGrantTypes(SecurityConstant.GRANT_TYPE_PASSWORD, SecurityConstant.REFRESH_TOKEN)
.resourceIds(SecurityConstant.RESOURCE_ID);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
{
endpoints.tokenStore(tokenStore)
.userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenEnhancer(new CustomTokenEnhancer());
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception
{
super.configure(security);
security.checkTokenAccess("permitAll()");
}
}
ResourceServerConfig
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
{
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources)
{
resources.tokenStore(tokenStore)
.resourceId(SecurityConstant.RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception
{
http.formLogin().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers(Uri.DIET + "/**").authenticated()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
And i getting an error message like this when i try to login in the web
Failed to load resource: the server responded with a status of 403 ()
Access to XMLHttpRequest at 'https://---.---.---:8443/folder/oauth/token' from origin 'https:// ---.---.---' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Are you sending the header "with credentials" on the client side? If it`s an angular 7 app you have to allow the with credentials header on the server side, adding on the cors configuration, and add an interceptor on the client side for every http client request. Besides that, you should not let "*" as allowed origins or the with credentials header will not work.
On Angular create this:
#Injectable()
export class CredentialsInterceptor implements HttpInterceptor {
constructor() {}
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
request = request = request.clone({
withCredentials: true
});
return next.handle(request);
}
}
And add to app.module:
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: CredentialsInterceptor,
multi: true
}
Another problem could be the order of the cors filter, it should be before the security filter on filterChain. You can handle it, with something like this:
#Bean
FilterRegistrationBean<CorsFilter> corsFilter(CorsConfigurationSource
corsConfigurationSource)
{
CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>
();
bean.setFilter(corsFilter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}

Spring security - implement oauth2 sso

I want to implement central authentication system with spring security and oauth2 sso. In other words, I have a spring boot application that is responsible for authorization and one simple client. My client has rest API. First I get token from the authorization server, then send a request to client API with an authorization header contains bearer token from above request. But this request always gets me server login page.
Here is the implementation of the server and the client:
Server
AuthorizationServerConfig.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("SampleClientId")
.secret("{noop}secret")
.authorizedGrantTypes("password")
.scopes("user_info")
.autoApprove(true);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
}
ApplicationConfig:
#SpringBootApplication
#EnableResourceServer
public class ApplicationConfig extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ApplicationConfig.class, args);
}
}
SecurityConfig:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//this is just example
auth.inMemoryAuthentication().withUser("user").password("{noop}1234").roles("user");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/token")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
application.yml:
server:
port: 8900
servlet:
context-path: /auth
Client:
ApplicationConfig:
#SpringBootApplication
public class ApplicationConfig {
public static void main(String[] args) {
SpringApplication.run(ApplicationConfig.class, args);
}
}
SecurityConfig:
#Configuration
#EnableOAuth2Sso
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/login**")
.permitAll()
.anyRequest()
.authenticated();
}
}
TestController:
#RestController
public class HomeController {
#GetMapping("/")
public String index() {
return "home";
}
#RequestMapping("/admin")
public String admin() {
return "admin";
}
}
application.yml:
server:
port: 9000
servlet:
context-path: /client1
security:
basic:
enabled: false
oauth2:
client:
clientId: SampleClientId
clientSecret: secret
accessTokenUri: http://localhost:8900/auth/oauth/token
userAuthorizationUri: http://localhost:8900/auth/oauth/authorize
resource:
userInfoUri: http://localhost:8900/auth/user/me
First, I send client_id and secret code along side with username, password and grant_type to localhost:8900/auth/oauth/token and get a result like this:
{
"access_token": "603b505f-e701-43d0-b8b8-976a2178f7ea",
"token_type": "bearer",
"expires_in": 43199,
"scope": "user_info"
}
Now, I pickup above token and send a request to localhost:9000/client1/admin
with header contains above token. But it seems the client application ignores the header and shows server login page as result. How can I fix this problem?
#EnableOAuth2Sso is an annotation for using OAuth 2.0 as an end-user authentication mechanism (e.g. "A Login with Google" button). This annotation is wiring your app to redirect to a login page on your authorization server where you would log in and then get redirected back to your app.
If this is your intent, then you'll need to update your Authorization Server to support the authorization_code grant flow instead of the password grant flow.
However, if your client is strictly a REST API, then you are more likely to need to wire the client using #EnableResourceServer instead of #EnableOAuth2Sso. A Resource Server is what takes a token as authorization, via the Authorization HTTP header.

Combining Oauth2 with formlogin and actuator security in Spring Boot

I am using Spring Boot 1.5.9 and have an application that has an API that uses OAuth2 client credentials, with formlogin for a CMS that uses Thymeleaf in the same Spring Boot application.
For this to work, I have the following bean to configure the form login:
#Configuration
public class WebSecurityGlobalConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// api security is handled elsewhere (See OAuth2ServerConfiguration)
.antMatchers("/api/**", "/oauth/**", "/management/**")
.permitAll()
// end api security
.anyRequest().hasRole(UserRole.ADMIN.name())
.and()
.formLogin().loginPage("/login")
.permitAll()
.and()
.logout().permitAll();
}
}
So for the form login part, I declare everything related to API, Oauth and /management (the custom context-path I have set in application.properties for the actuator endpoints):
management.context-path=/management
management.security.roles=ADMIN
For Oauth2, I have this:
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "my-app-service";
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**")
.permitAll()
.and()
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.authorizeRequests()
.antMatchers("/management/health", "/management/info").permitAll()
.antMatchers("/management/**").hasRole(UserRole.ADMIN.name())
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private TokenStore tokenStore;
#Autowired
private SecurityConfiguration securityConfiguration;
// NOTE: If you set a new validity, you need to clear the 'oauth_access_token' table
// in the database. Only new tokens get the new validity.
#Value("${myapp.security.oauth.access-token-validity-seconds:43200}") // 12 hours by default
private int accessTokenValiditySeconds;
#Value("${myapp.security.oauth.refresh-token-validity-seconds:2592000}") // 30 days by default
private int refreshTokenValiditySeconds;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(securityConfiguration.getMobileAppClientId())
.authorizedGrantTypes("password", "refresh_token")
.scopes("mobile_app")
.resourceIds(RESOURCE_ID)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.secret(passwordEncoder.encode(securityConfiguration.getMobileAppClientSecret()));
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).
authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
}
I want the following behaviour:
If user has role ADMIN by using an Oauth2 access token, all actuator endpoints must be accessible
If the user does not have this ADMIN role, only /health and /info should be accessible (If ADMIN, /health should show extra info like it is by default)
The current behaviour:
The info and health endpoints can be viewed by everybody, but as ADMIN, you don't get extra info. For the other endpoints, I get a 401 if I try with an access token of an ADMIN user with:
{
"timestamp": "2018-01-30T13:45:26.625+0000",
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource.",
"path": "/management/beans"
}
If I set management.security.enabled=false then the ADMIN user has access, but all non-ADMIN users also have access.
What should I change to get the wanted behaviour?
I managed to make it work with the following in the configure method of ResourceServerConfiguration :
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.and()
.requestMatchers()
.antMatchers("/management/**")
.and()
.authorizeRequests()
.antMatchers("/management/health", "/management/info").permitAll()
.antMatchers("/management/**").hasRole(UserRole.ADMIN.name())
.anyRequest()
.authenticated()
Using multiple antMatchers directly on the http object does not work, you need to first use requestMatchers

OAuth2 with Spring Boot REST application - cannot access resource with token

I want to use OAuth2 for my REST spring boot project. Using some examples I have created configuration for OAuth2:
#Configuration
public class OAuth2Configuration {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources
.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.anonymous().disable()
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// #formatter:off
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password", "refresh_token", "trust")
.authorities("USER")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret("clientsecret")
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(3600);
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
}
This is my SecurityConfiguration class:
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
.authorizeRequests().antMatchers("/api/register").permitAll()
.and()
.authorizeRequests().antMatchers("/api/free").permitAll()
.and()
.authorizeRequests().antMatchers("/oauth/token").permitAll()
.and()
.authorizeRequests().antMatchers("/api/secured").hasRole("USER")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I tried to check my application with 2 simple requests:
#RequestMapping(value = "/api/secured", method = RequestMethod.GET)
public String checkSecured(){
return "Authorization is ok";
}
#RequestMapping(value = "/api/free", method = RequestMethod.GET)
public String checkFree(){
return "Free from authorization";
}
Firstly I checked two requests:
/api/free returned code 200 and the string "Free from authorization"
/api/secured returned {"timestamp":1487451065106,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/secured"}
And it seems that they work fine.
Then I got access_token (using credentials from my users database)
/oauth/token?grant_type=password&username=emaila&password=emailo
Response:
{"access_token":"3344669f-c66c-4161-9516-d7e2f31a32e8","token_type":"bearer","refresh_token":"c71c17e4-45ba-458c-9d98-574de33d1859","expires_in":1199,"scope":"read write"}
Then I tried to send a request (with the token I got) for resource which requires authentication:
/api/secured?access_token=3344669f-c66c-4161-9516-d7e2f31a32e8
Here is response:
{"timestamp":1487451630224,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/secured"}
I cannot understand why access is denied. I am not sure in configurations and it seems that they are incorrect. Also I still do not clearly understand relationships of methods configure(HttpSecurity http) in class which extends WebSecurityConfigurerAdapter and in another which extends ResourceServerConfigurerAdapter.
Thank you for any help!
If you are using spring boot 1.5.1 or recently updated to it, note that they changed the filter order for spring security oauth2 (Spring Boot 1.5 Release Notes).
According to the release notes, try to add the following property to application.properties/yml, after doing that the resource server filters will be used after your other filters as a fallback - this should cause the authorization to be accepted before falling to the resource server:
security.oauth2.resource.filter-order = 3
You can find a good answer for your other questions here: https://stackoverflow.com/questions/28537181

Categories