Spring Security 5 populating authorities based on JWT claims - java

As I see Spring Security OAuth2.x project was moved to Spring Security 5.2.x. I try to implement authorization and resource server in new way. Everythin is working correctly except one thing - #PreAuthorize annotation. When I try to use this with standard #PreAuthorize("hasRole('ROLE_USER')") I always get forbidden. What I see is that the Principal object which is type of org.springframework.security.oauth2.jwt.Jwt is not able to resolve authorities and I have no idea why.
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken#44915f5f: Principal: org.springframework.security.oauth2.jwt.Jwt#2cfdbd3; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#ffffa64e: RemoteIpAddress: 172.19.0.1; SessionId: null; Granted Authorities: SCOPE_read, SCOPE_write
And claims after casting it to Jwt
{user_name=user, scope=["read","write"], exp=2019-12-18T13:19:29Z, iat=2019-12-18T13:19:28Z, authorities=["ROLE_USER","READ_ONLY"], client_id=sampleClientId}
Security Server Configuration
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public KeyPair keyPair() {
ClassPathResource ksFile = new ClassPathResource("mytest.jks");
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(ksFile, "mypass".toCharArray());
return keyStoreKeyFactory.getKeyPair("mytest");
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair());
return converter;
}
#Bean
public JWKSet jwkSet() {
RSAKey key = new Builder((RSAPublicKey) keyPair().getPublic()).build();
return new JWKSet(key);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
public SecurityConfiguration(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/.well-known/jwks.json")
.permitAll()
.anyRequest()
.authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
Resource server configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResuorceServerConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
Maybe someone had similar issue?

By default, the resource server populates the authorities based on the "scope" claim.
If the Jwt contains a claim with the name "scope" or "scp", then Spring Security will use the value in that claim to construct the authorities by prefixing each value with "SCOPE_".
In your example, one of the claims is scope=["read","write"].
This means that the authority list will consist of "SCOPE_read" and "SCOPE_write".
You can modify the default authority mapping behaviour by providing a custom authentication converter in your security configuration.
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(getJwtAuthenticationConverter());
Then in your implementation of getJwtAuthenticationConverter, you can configure how the Jwt maps to the list of authorities.
Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
// custom logic
});
return converter;
}

Add this to your SecurityConfig
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
And the converter
private JwtAuthenticationConverter jwtAuthenticationConverter() {
// create a custom JWT converter to map the "roles" from the token as granted authorities
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}

Related

how to sign in using both basic authentication with own set of users and ldap authentication?

For now I have working authentication system, and now trying to add here ldap authentication.
First I should try to signin using my set of users, then if not found, try from ldap server.
But i don't have no idea how to do it.
AuthController:
#RestController
#RequestMapping("/api/v1/auth")
public class AuthController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
PasswordEncoder encoder;
#Autowired
JwtUtils jwtUtils;
#Autowired
UserService userService;
#PostMapping("/signin")
public ResponseEntity<JwtResponse> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
if (!userDetails.getIsActive()) {
throw new RedoException("Данный пользователь заблокирован");
}
String jwt = jwtUtils.generateJwtToken(authentication);
List<String> permissions = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
Set<String> groups = userService.getAllGroupsByUser(userDetails.getUsername());
return ResponseEntity.ok(new JwtResponse(jwt,
userDetails.getUsername(),
userDetails.getEmail(),
permissions,
userService.getUserInfoByUsername(userDetails.getUsername()),
groups));
}
}
And WebSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Autowired
UserDetailsServiceImpl userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
configuration.setAllowedOriginPatterns(Collections.singletonList("*")); //set access from all domains
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Configuration
#Order(1)
public class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.antMatcher("/api/v1/external/**")
.authorizeRequests()
.anyRequest().authenticated().and()
.httpBasic();
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
#Configuration
#Order(2)
public class JwtAuthConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/v1/auth/**", "/api/v1/doc/", "/api/v1/check/", "/swagger-ui/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
}
and i used other project to test only ldap:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword");
}
}
some application properties:
spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=8389
So how use them both.

Spring Oauth2 with client_credentials does not validate user

I'm trying to setup a simple Oauth2 Server with Spring Boot with only client_credentials flow for now, using in memory users and clients etc. Basically the most basic project so I can build on it. Haven't used Oauth2 with Spring for some time so I'm a bit stuck. I can get access tokens but it seems Spring does not actually validate the username/password sent by the client. Here are the two config classes (AuthorizationServerConfigurerAdapter, WebSecurityConfigurerAdapter):
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userService;
#Value("${jwt.signing-key}")
private String jwtSigningKey;
#Value("${jwt.accessTokenValidititySeconds}")
private int accessTokenValiditySeconds;
#Value("${jwt.refreshTokenValiditySeconds}")
private int refreshTokenValiditySeconds;
public OAuthConfiguration(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserDetailsService userService) {
this.authenticationManager = authenticationManager;
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test-client-id")
.secret(passwordEncoder.encode("test-client-secret"))
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.authorizedGrantTypes("client_credentials")
.scopes("read", "write")
.resourceIds("api");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.accessTokenConverter(accessTokenConverter())
.userDetailsService(userService)
.authenticationManager(authenticationManager);
}
#Bean
JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(jwtSigningKey);
return converter;
}
}
and:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
public WebSecurityConfiguration(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) {
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
}
#Bean
public DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1")
.password("$2a$10$sWszOXuTlN0amQi8vXp4cerb.tJUQo.4FzLAnTCsSqChsYhlLdQWW")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler());
}
}
So I can call the "/oauth/token" endpoint with any username/password I get an access token. However the client ID and client secret are being validated.
Any help is appreciated
It's the default behaviour with grant_type client_credentials. So, you don't need to pass the username and password to the token endpoint with this grant_type. In this flow, AuthorizationServer will only validate the client Id and client secret.
POST /token
Host: authorization-server.com
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

How to configure Spring Security OAuth2 to verify my own JWT token and Google OpenID connection?

I currently have a microservices architecture secured by an authentication server and a resource server using Oauth2 protocol with my users stored in a database. To authenticate I use spring-security-oauth2 configured to return JWT tokens and these tokens travel in the request headers between my microservices.
Now I have a new requirement that I would like to be able to authenticate with Google using OpenID connect as well.
I have been researching how to do it but I haven't found a solution using spring-security-oauth2 because I want to keep my security core and avoid change it for this new requirement, this means I want to use my JWT certificate to validate the tokens that are flying between my microservices.
I suppose that I have to register the user with Google OpenId flow and use it to login with my current flow, but I don't know how to integrate with the web frontend, and how to register them.
I'm working with Spring Boot 1.5.4 and Spring Cloud 1.2.1
This is my current configuration:
MyAuthorizationServer.java
#Configuration
#EnableAuthorizationServer
public class MyAuthorizationServer extends AuthorizationServerConfigurerAdapter {
#Value("${keyStore.location}")
private Resource resource;
#Value("${keyStore.password}")
private String password;
#Value("${keyStore.alias}")
private String alias;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(resource, password.toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair(alias));
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
}
MyResourceServer.java
#Configuration
#EnableResourceServer
public class MyResourceServer extends ResourceServerConfigurerAdapter {
#Autowired
private DefaultTokenServices tokenServices;
#Override
public void configure(final HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/admin/** ").permitAll()
.anyRequest().authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices);
}
}
MyWebServerSecurity.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class MyWebServerSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/oauth/authorize").permitAll()
.anyRequest().authenticated().and().csrf().disable();
}
}
Thanks for your help.
Regards

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

Spring security OAuth stackoverflowException

I would like to use Spring security with OAuth and JWT tokens.
My current configurations are:
#Configuration
#EnableResourceServer
public class OAuth2ServerConfig {
#Configuration
#EnableWebSecurity
protected static class ResourceServer extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.anonymous().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler()) // handle access denied in general (for example comming from #PreAuthorization
.authenticationEntryPoint(entryPointBean()) // handle authentication exceptions for unauthorized calls.
.and()
.authorizeRequests()
// only allow this three endpoint to NOT be authenticated.
.antMatchers(HttpMethod.POST, "/users").permitAll()
.antMatchers(HttpMethod.POST, "/users/authenticate").permitAll()
.antMatchers(HttpMethod.GET, "/users/inviationCode/{code}").permitAll()
.antMatchers(HttpMethod.POST, "/**").fullyAuthenticated()
.antMatchers(HttpMethod.GET, "/**").fullyAuthenticated()
.antMatchers(HttpMethod.PUT, "/**").fullyAuthenticated()
.antMatchers(HttpMethod.DELETE, "/**").fullyAuthenticated()
.antMatchers(HttpMethod.OPTIONS, "/**").fullyAuthenticated()
.and()
.addFilterBefore(filterBean(), AbstractPreAuthenticatedProcessingFilter.class)
.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/oauth/**")))
.authorizeRequests().anyRequest().authenticated().expressionHandler(new OAuth2WebSecurityExpressionHandler())
.and()
.csrf().disable(); // for chrome/FF plugins to work. for now we shouldn't face any problem since there is no point that JS can be injected into our page...
// #formatter:on
}
#Bean(name="authenticationManager")
#Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Autowired
AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedExceptionHandler();
}
#Bean
#Autowired
AuthenticationEntryPoint entryPointBean() {
return new UnauthorizedEntryPoint();
}
#Bean
#Autowired
GenericFilterBean filterBean() {
return new XAuthTokenFilter();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean(name="userAuthenticationManager")
public UserAuthenticationService userAuthenticationManager() throws Exception {
return new UserAuthenticationService();
}
}
#Configuration
#EnableAuthorizationServer
public static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManager")
private AuthenticationManager authenticationManager;
#Autowired
#Qualifier("restDataSource")
private BasicDataSource restDataSource;
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
return new JwtAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess(
"hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).accessTokenConverter(accessTokenConverter());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.secret("secret");
}
}
}
Those configurations are based on the official spring github repo
The problem I am facing now, is that whenever I try to obtain a token using this url:
http://myapplication.com/test/oauth/token?grant_type=password
I am getting the following error:
java.lang.StackOverflowError
at org.apache.commons.logging.impl.Jdk14Logger.isDebugEnabled(Jdk14Logger.java:214)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:144)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:427)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:177)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:427)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:177)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:427)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:177)
This seems like a loop in the authentication process but to be honest I can find the root of it.
The flow I would like to use is the following:
User asks for a token passing user name, password and client (probably in base64). (METHOD POST)
User is being authenticated.
User is returned a JWT token.
User carries the token in the header.
Can someone advice on the appropriate configurations?
Best
Found the problem, it was specific to the authentication manager.
This is the working configuration for me:
#Configuration
#ComponentScan
#EnableResourceServer
#Import({SecurityConfig.class})
public class OAuth2ServerConfig {
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
#Qualifier("restDataSource")
private DataSource datasource;
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
return new JwtAccessTokenConverter();
}
#Bean
public CustomJwtTokenStore tokenStore() {
return new CustomJwtTokenStore();
}
// #Bean
// public JdbcTokenStore tokenStore() {
// return new JdbcTokenStore(datasource);
// }
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess(
"hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-trusted-client")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(60)
.and()
.withClient("my-client-with-registered-redirect")
.authorizedGrantTypes("authorization_code")
.authorities("ROLE_CLIENT")
.scopes("read", "trust")
.redirectUris("http://anywhere?key=value")
.and()
.withClient("my-client-with-secret")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.secret("secret");
}
}
}
with the security config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private AccessDeniedHandler accessDeniedHandler;
#Autowired
private GenericFilterBean filter;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/webjars/**", "/images/**", "/oauth/uncache_approvals", "/oauth/cache_approvals");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userAuthenticationManager()).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler) // handle access denied in general (for example comming from #PreAuthorization
.authenticationEntryPoint(authenticationEntryPoint) // handle authentication exceptions for unauthorized calls.
.and()
.authorizeRequests()
.antMatchers("/xxx/**").fullyAuthenticated()
.antMatchers("/xxx/**").fullyAuthenticated()
.antMatchers("/xxx/**").fullyAuthenticated()
.and()
.csrf().disable();;
}
#Bean
#Autowired
ApplicationListener<AbstractAuthenticationEvent> loggerBean() {
return new org.springframework.security.authentication.event.LoggerListener();
}
#Bean
#Autowired
AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedExceptionHandler();
}
#Bean
#Autowired
AuthenticationEntryPoint entryPointBean() {
return new UnauthorizedEntryPoint();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean(name="userAuthenticationManager")
public UserDetailsService userAuthenticationManager() throws Exception {
return new UserServiceImpl();
}
#Bean
public UserService userService() {
return new UserServiceImpl();
}
}

Categories