I got stucked on this authorization problem, no matter what i do i can't forward.
If i protect my resource with #PreAuthorize, i receive an access_denied in response.
The Rest Controller:
#RestController
#RequestMapping("/api/users")
public class UserRestController {
private UserService userService;
#GetMapping
#PreAuthorize("hasRole('USER')")
public ResponseEntity<Collection<User>> findAll() {
return ResponseEntity.status(HttpStatus.OK).body(userService.findAll());
}
}
The Websecurity:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().disable() // disable form authentication
.anonymous().disable() // disable anonymous user
.authorizeRequests().anyRequest().denyAll(); // denying all access
}
}
The Authorization server config:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private ApplicationConfigurationProperties configuration;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailOath2Service userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.tokenServices(tokenServices())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(configuration.getClientId())
.secret(configuration.getClientSecret())
.scopes("read", "write")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.resourceIds(RestApiResourceServerConfiguration.RESOURCE_ID);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
}
The resource server:
#Configuration
#EnableResourceServer
public class RestApiResourceServerConfiguration extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "restservice";
#Autowired
private DefaultTokenServices tokenServices;
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll().and().csrf().disable();
}
}
The User detail service:
#Service
public class UserDetailOath2Service implements UserDetailsService {
private final Logger LOGGER = Logger.getLogger(UserDetailOath2Service.class);
#Autowired
private UserRepository repository;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) {
LOGGER.info("Entering in loadUserByUsername " + username);
final User user = repository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
final List<SimpleGrantedAuthority> authorities = user.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
When i ask for http://localhost:8090/oauth/token i receive the token:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE1NDgxMTExNjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGZjODNiOTktMjZiNC00NWZkLWIxMGQtZDgxMzAzZDM2MjM4IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.rZAB_LmKuAN6R7i-7dUvYv4Q6vr8LhTNKgPMDVufFTc",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiI0ZmM4M2I5OS0yNmI0LTQ1ZmQtYjEwZC1kODEzMDNkMzYyMzgiLCJleHAiOjE1NTA2NTk5NjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGNiYWYyZWUtOTFhOC00N2Q2LTllZmEtYzA4ODI1NTI5MmQ3IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.41tdJ3Qc4nodc4ZAOr6dhYOa8XTqBOFQc9X1yM7NrGE",
"expires_in": 43199,
"scope": "read write",
"jti": "4fc83b99-26b4-45fd-b10d-d81303d36238"
}
So i take the token and try to call the protected resource:
GET /api/users HTTP/1.1
Host: localhost:8090
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE1NDgxMTExNjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGZjODNiOTktMjZiNC00NWZkLWIxMGQtZDgxMzAzZDM2MjM4IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.rZAB_LmKuAN6R7i-7dUvYv4Q6vr8LhTNKgPMDVufFTc
The result is this:
{
"error": "access_denied",
"error_description": "Access is denied"
}
The user i have generate the token has a USER role.
Please, can someone help me to find what i doing wrong?
Thanks.
The generated token has a USER authority, not a USER role.
There is a subtle but significant difference between a role and a authority: https://www.baeldung.com/spring-security-granted-authority-vs-role
Change #PreAuthorize("hasRole('USER')") to #PreAuthorize("hasAuthority('USER')") or grant the authority ROLE_USER to get the role USER
It seems that you haven't provided the resource access to the role.
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.csrf().disable().authorizeRequests()
// This is needed to enable swagger-ui interface.
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAuthority("ROLE_TRUSTED_CLIENT")
// #formatter:on
}
Ensure that you are not overwriting the authority to some other role/permission. So configuration like
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.csrf().disable().authorizeRequests()
// This is needed to enable swagger-ui interface.
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAuthority("ROLE_TRUSTED_CLIENT")
.antMatchers("/api/v1/**").hasAuthority("ROLE_USER");
// #formatter:on
}
will have a problem. The permission is only now granted to ROLE_USER and the ROLE_TRUSTED_CLIENT.
To provide multiple roles access please use following
http.csrf().disable().authorizeRequests()
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAnyAuthority("ROLE_TRUSTED_CLIENT", "ROLE_USER")
.anyRequest().authenticated();
Related
I have a hard time on a personal study project. Difficulty implementing oauth2, whenever I try to get access token it returns me
2019-11-19 22:01:35.398 ERROR 4705 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError: null
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:448) ~[spring-security-config-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:449) ~[spring-security-config-5.2.0.RELEASE.jar:5.2.0.RELEASE]
2019-11-19 22:01:35.426 WARN 4705 --- [nio-8080-exec-2] o.s.web.servlet.PageNotFound : No mapping for POST /error
my project uses Version Spring Boot v2.2.0.RELEASE and Java 1.8
Application.propeties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=test
spring.datasource.password=test123
spring.jpa.hibernate.ddl-auto=update
spring.datasource.testWhileIdle = true
spring.datasource.timeBetweenEvictionRunsMillis = 3600000
spring.datasource.validationQuery = SELECT 1
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
spring.jpa.show-sql=true
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("angular")
.secret(passwordEncoder.encode("angular"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(1800)
.refreshTokenValiditySeconds(3600 * 24)
.and()
.withClient("admin")
.secret("admin")
.scopes("read")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(1800)
.refreshTokenValiditySeconds(3600 * 24);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("financas");
return accessTokenConverter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
ResourceServerConfig
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/pagamento").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true);
}
#Bean
public static PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
SecurityConfig
#EnableWebSecurity
#EnableAuthorizationServer
#EnableResourceServer
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
}
now my refresh token classes
RefreshTokenPostProcessor
#ControllerAdvice
public class RefreshTokenPostProcessor implements ResponseBodyAdvice<OAuth2AccessToken> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getMethod().getName().equals("postAccessToken");
}
#Override
public OAuth2AccessToken beforeBodyWrite(OAuth2AccessToken body,
MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest req = ((ServletServerHttpRequest)request).getServletRequest();
HttpServletResponse resp = ((ServletServerHttpResponse)response).getServletResponse();
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) body;
String refreshToken = body.getRefreshToken().getValue();
adicionaRefreshTokenNoCookie(refreshToken, req, resp);
removerRefreshTokenDoBody(token);
return body;
}
private void removerRefreshTokenDoBody(DefaultOAuth2AccessToken token) {
token.setRefreshToken(null);
}
private void adicionaRefreshTokenNoCookie(String refreshToken, HttpServletRequest req, HttpServletResponse resp) {
Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken);
refreshTokenCookie.setHttpOnly(true);
refreshTokenCookie.setSecure(false); //TODO: change in production
refreshTokenCookie.setPath(req.getContextPath() + "/oauth/token");
refreshTokenCookie.setMaxAge(2592000);
resp.addCookie(refreshTokenCookie);
}
}
RefreshTokenPreProcessorFilter
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RefreshTokenPreProcessorFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if ("/oauth/token".equalsIgnoreCase(req.getRequestURI())
&& "refresh_token".equals(req.getParameter("grant_type"))
&& req.getCookies() != null) {
for (Cookie cookie : req.getCookies()) {
if (cookie.getName().equals("refreshToken")) {
String refreshToken = cookie.getValue();
req = new MyServletRequestWrapper(req, refreshToken);
}
}
}
chain.doFilter(req, response);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
static class MyServletRequestWrapper extends HttpServletRequestWrapper {
private String refreshToken;
public MyServletRequestWrapper(HttpServletRequest request, String refreshToken) {
super(request);
}
#Override
public Map<String, String[]> getParameterMap() {
ParameterMap<String, String[]> map = new ParameterMap<>(getRequest().getParameterMap());
map.put("refresh_token", new String[] {refreshToken});
map.setLocked(true);
return map;
}
}
}
SpringBootApplication
#SpringBootApplication
#WebAppConfiguration
public class FinancaApplication {
public static void main(String[] args) {
SpringApplication.run(FinancaApplication.class, args);
}
}
Remembering that my authentication is in the database and I am using PasswordEncoder on the database password and authentication passwords, I would like some help setting up the server and where my error is because I cannot generate an access token, the error returned is just this one in the console. thankful
I like your code Felipe - it's a very nicely written API - the problem is that it attempts to issue tokens also, which an API should not do.
I'd recommend walking through my tutorial, to understand the roles of API, UI and Authorization Server:
https://authguidance.com/2017/09/24/basicspa-overview/
Once you've done that I think you'll be able to adapt your API and fix your own problems - feel free to post back any follow up questions though.
In the real world almost all companies use a third party (cloud) system as the Authorization Server:
https://authguidance.com/2019/09/15/developer-domain-setup/
You will learn OAuth much faster this way - and it is free.
You then only need to focus on integrating your APIs and UIs with standards compliant endpoints like this:
https://authguidance.com/2019/03/24/java-spring-boot-api-oauth-coding/
Happy to answer follow up questions if it helps ..
I have a Spring Security configuration where I want requests to /stats to be secured in a different realm than all other requests, i.e. when I navigate to / I will be prompted with a HTTP basic login form and after I login and navigate to /stats there is another HTTP basic login form since I'm not yet logged in in that realm. The problem with my current configuration is that once I'm logged in at / and navigate to /stats I get a 403 as I'm already logged in but with a different authority. Here is my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity
public class PortalServiceSecurityConfig {
#Configuration
#Order(1)
static class StatsWebSecConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").authorities("STATS");
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
final BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName("stats realm");
return entryPoint;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/stats").authorizeRequests()
.antMatchers("/stats").hasAuthority("STATS")
.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint());
}
}
#Configuration
#Order(2)
static class PortalServiceConfig extends WebSecurityConfigurerAdapter {
#Autowired
private WebserviceAuthenticationConfiguration configService;
#Autowired
private SoapFaultDetailtConverter soapFaulDetailtConverter;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
protected UserDetailsService userDetailsService() {
return new DefaultPortalSecurityService(configService);
}
#Bean
SecurityWebApplicationInitializer securityWebApplicationInitializer() {
return new SecurityWebApplicationInitializer();
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
final BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName("ws realm");
return entryPoint;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().hasAuthority("WEBSERVICE")
.and()
.csrf().disable()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint());
}
}
}
I am trying to secure my Spring Boot Rest API using OAuth2.0 with Spring security and want to store OAuth tokens (Access Token and Refresh Token) in JDBC database.
For Username and Password validation i have created CustomUserDetailService.
I am getting following error-
{
"timestamp": 1480042650103,
"status": 401,
"error": "Unauthorized",
"message": "Bad credentials",
"path": "/oauth/token"
}
My Code is -
Oauth2Config
#Configuration
#EnableAuthorizationServer
#EnableResourceServer
public class AuthorizationServerConfiguration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.authorizeRequests().anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
#Autowired
private CustomUserDetailService customUserDetailService;
#Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authorizationCodeServices(authorizationCodeServices())
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.approvalStoreDisabled()
.userDetailsService(customUserDetailService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
WebSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
CustomUserDetailService
#Service
public class CustomUserDetailService
implements UserDetailsService {
#Override
public UserDetailsService loadUserByUsername(String username) throws UsernameNotFoundException {
// I tested this logic and works fine so i avoid this lines
return userDetailsService;
}
}
application.properties
security.oauth2.client.client-id=my-trusted-client
security.oauth2.client.client-secret=secret
security.oauth2.client.authorized-grant-types=password,refresh_token,authorization_code,implicit
security.oauth2.client.scope=read,write,trust
security.oauth2.client.resource-ids=oauth2-resource
security.oauth2.client.access-token-validity-seconds=120
security.oauth2.client.refresh-token-validity-seconds=600
Please suggest me how can i solve this error ??
We have a dedicated authorization server extending AuthorizationServerConfigurerAdapter, where we have set authorities overriding void configure(ClientDetailsServiceConfigurer clients) method.
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Value('${oauth.clientId}')
private String clientId
#Value('${oauth.secret:}')
private String secret
#Value('${oauth.resourceId}')
private String resourceId
#Autowired
#Qualifier('authenticationManagerBean')
private AuthenticationManager authenticationManager
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
return new JwtAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()")
oauthServer.allowFormAuthenticationForClients()
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(secret)
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("USER", "ADMIN")
.scopes("read", "write", "trust")
.resourceIds(resourceId)
}
Now how to use the authorities in the resource server for role based authorization.
We are able to authenticate via authorization server generated token.
Need help.
In the resource server you should extend the ResourceServerConfigurerAdapter to configure the requestMatchers and set the role for each resource.
#Configuration
#EnableResourceServer
public class OAuth2Config extends ResourceServerConfigurerAdapter {
#Value("${keys.public}")
private String publicKey;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/**")
.and()
.authorizeRequests()
.antMatchers("/service1/**").access("#oauth2.hasScope('ADMIN')")
.antMatchers("/service2/**").access("#oauth2.hasScope('USER')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setVerifierKey(publicKey);
return tokenConverter;
}
}
You have received a token from the auth server. You can now use that token to make another request to the auth server to retrieve the user object. This json object would contain roles(authority).
The request would look like as follows.
curl -H "Authorization: Bearer 2a953581-e9c9-4278-b42e-8af925f49a99"
http://localhost:9999/uaa/user
In order to do this, you need to create user service endpoint and implement UserDetailsService also.
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#Bean
UserDetailsService userDetailsService.....
The role list is created and set in the org.springframework.security.core.userdetailsin the UserDetailsService.User as follows.
AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN"));
I'm in the process of building separate resource server and authhorization server. For now i'm using the user-info-uri in resource server to extract the Principal from the authorization server matching the access-token, with config:
spring:
oauth2:
resource:
userInfoUri: http://localhost:9999/uaa/user
In the resource server I have protected endpoints, based on role, as follows:
http
.authorizeRequests()
.antMatchers("/invoices/**").hasRole("END_USER")
.anyRequest().authenticated();
When I manually access the user-info-uri, I can see that the the authorities contain:
"authority": "ROLE_END_USER"
But when I try to access the /invoices resource I get an Access-Denied exception, and in the log I see:
OAuth2Authentication#bc5074a8: Principal: my-login; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: ROLE_USER
Authoriteis = "ROLE_USER". Where does that come from, should'nt it be "ROLE_END_USER" at this point also?
I've seen implementations using a shared database for the storage of tokens, is that really necessary for what I want to achive?
Resource Server:
#SpringBootApplication
#EnableOAuth2Resource
public class EndUserResourceServiceApplication extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/invoices/**").hasRole("END_USER")
.anyRequest().authenticated();
}
public static void main(String[] args) {
SpringApplication.run(EndUserResourceServiceApplication.class, args);
}
}
Auth Server:
#SpringBootApplication
#RestController
#EnableResourceServer
public class ApiAuthServerApplication extends ResourceServerConfigurerAdapter {
#Configuration
#EnableWebSecurity
#Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Bean(name = "authenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.userDetailsService(this.userDetailsService)
.tokenStore(this.tokenStore);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-client")
.secret("our_s3cret")
.authorities("ROLE_CLIENT")
.authorizedGrantTypes("implicit", "password", "refresh_token")
.redirectUris("http://anywhere")
.scopes("read")
.autoApprove(true);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
public static void main(String[] args) {
SpringApplication.run(ApiAuthServerApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
}
Summary:
Can I use user-info-uri for validating access-tokens and to use "hasRole"
Is it necessary to use shared database for token storage when using separate resource server and authorization server?
I ended up using a shared token store, which works well at the moment