Spring Boot 2 Oauth how to implement Implicit Code Flow - spring

Currently I am in the process of introducing OAuth to my Spring application, but I have not luck to integrate it correctly. I am using Spring Boot 2.
My requirements are:
Authorization and resource server are the same application running on the same server
Implicit, Authorisation Code Grant and Resource owner credentials grant flows need to be supported
The API I want to secure lives under "/api/v1/"
None of the flows is working correctly.. What I achieved so far is based on the tutorial by https://www.devglan.com/spring-security/spring-oauth2-role-based-authorization and this answer: https://stackoverflow.com/a/52386009/4454752
So my AuthorizationServer looks like this:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("as466gf");
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("my-client-id")
.authorizedGrantTypes("authorization_code", "implicit", "refresh_token", "password")
.authorities("ADMIN")
.scopes("all")
.resourceIds("product_api")
.secret("$2a$10$jfAHmk4szDU/t1qLGlFTLukuBZL0ZHZGUJQICePjjyq6IrLOS934.")
.redirectUris("https://example.com")
.accessTokenValiditySeconds(7200)
.refreshTokenValiditySeconds(7200);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter());
}
}
Then the ResourceServer
#Configuration
#EnableResourceServer
#Order(2)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("product_api");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/**")
.and().authorizeRequests()
.antMatchers("/**").permitAll()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
And last the WebSecurityConfig
#Configuration
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userDetailService")
private UserDetailService userDetailsService;
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#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 {
http.authorizeRequests()
.antMatchers("/api/v1/**")
.hasAnyRole("ADMIN", "USER").and()
.httpBasic().and().formLogin().and().authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
}
My UserDetails and the complete user management is already set up, so no need to change something here.
Now to the use cases.
If access "/oauth/token":
curl --request POST \
--url http://localhost:8080/oauth/token \
--header 'authorization: Basic bXktY2xpZW50Om15LXNlY3JldA==' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'grant_type=password&username=admin&password=test'
So with the my-client:my-secret I get an response with access token and refresh token. But if I want to use the access token on my API I get Access denied. If I check the token with /oauth/check_token it says that the token is valid.
Same problem if I use "/oauth/authorize" (Implicit Flow). I know that I need the login page from spring, to make it work, that's why I added formLogin() in WebSecurityConfig. But If I query http://localhost:8080/oauth/authorize?response_type=token&client_id=my-client&redirect_uri=https://example.com I get redirected to /login I log in,then get the access token from the redirect url, but again if I use this token I get the 401 error.
The endpoint I want to access is handled by the following controller:
#RestController
#RequestMapping(value = "/api/v1/user")
#CrossOrigin(origins = "*")
public class UserController {
private static final Logger LOGGER = LogManager.getLogger(UserController.class);
public static final String ROLE_USER = "ROLE_USER";
private final AuthenticationFacade authenticationFacade;
private final UserService userService;
#Autowired
public UserController(AuthenticationFacade authenticationFacade,
UserService userService) {
this.authenticationFacade = authenticationFacade;
this.userService = userService;
}
#RequestMapping(value = "/me", method = RequestMethod.GET)
public Optional<User> getCurrentUser() {
LOGGER.info("Requesting /api/v1/user/me");
return userService.findByUsername((String) authenticationFacade.getAuthentication().getPrincipal());
}
}
I am pretty sure that something with the Security configuration is messed up, but I have no idea what it could be. I looked trough a lot of guides online, but I did not find a single one which explained all the authorization code combined. I think it might be a small bug with authenticating the URLs but I have no clue what it could be.
I would be very happy if someone knows an answer for this.

Move your configure() implementation from WebSecurityConfig to Resource Server configure() method as below
http.authorizeRequests()
.antMatchers("/api/v1/**")
.hasAnyRole("ADMIN", "USER")
Rest of the configurations in configure() method are not required

Related

How to permit endpoints in spring authorization server?

I have a spring boot oauth2 authorization server which will provide and authorize tokens. I also want to provide endpoints for user creation. Can you tell me how to permit these endpoints for non-authenticated users? I tried following configuration:
#Configuration
#EnableAuthorizationServer
#RequiredArgsConstructor
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
private final UserDetailsService userDetailsServiceImpl;
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO persist clients details
clients.inMemory()
.withClient("browser")
.authorizedGrantTypes("refresh_token", "password")
.scopes("ui");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder);
}
}
and authorization server configuration:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsServiceImpl;
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> {
authorizeRequests
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated();
});
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsServiceImpl)
.passwordEncoder(passwordEncoder());
}
}
and here is endpoint which I want permit:
#RestController
#RequestMapping(path = "/user")
#RequiredArgsConstructor
public class UserController {
private final UserService userService;
#PostMapping
public UUID create(#RequestBody UserDto userDto) {
return userService.create(userDto);
}
}
With these configurations I always got response:
{
"timestamp": "2019-12-28T16:01:09.135+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/user"
}
I am using spring boot 2. Thank you in advice.
You need to disable CSRF in your AuthorizationConfig class. Try this configuration :
http.authorizeRequests(authorizeRequests -> {
authorizeRequests
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated();
}).csrf(csrf -> {
csrf.disable();
});
For more information about CSRF, check this website: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF).
Basically, you don't want to allow anybody to POST information on your website, so you allow to POST only if the user can provide a token indicating that he is using your website to POST (the token is provided by your server). In many web applications now, you can disable it, as you are POSTing from many locations... But don't forget the security of your website.

How to renew access token with the refresh token in oauth2 in spring?

I am very new to spring and it is my first attempt at spring security with oauth2. I have implemented OAuth2 with spring security and I do get the access token and the refresh token. However, while sending the refresh token to get the new access token I got "o.s.s.o.provider.endpoint.TokenEndpoint - IllegalStateException, UserDetailsService is required."
The solution to similar problem by other users appeared to be attaching UserDetailsService with the endpoint.
So I did the same and now when I try to send the request to with grant_type: refresh_token and refresh_token: THE TOKEN along with the client id and secret, I get an error that the user was not found.
Please refer the WebSecurityConfiguration class below:
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean ();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure (HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated();
}
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Please refer the AuthorizationServerConfiguration class below:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
.userDetailsService(userDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
Please refer the ResourceServerConfiguration class below:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("scout").tokenStore(tokenStore());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests (). antMatchers ("/oauth/token", "/oauth/authorize **").permitAll();
// .anyRequest (). authenticated ();
http.requestMatchers (). antMatchers ("/api/patients/**") // Deny access to "/ private"
.and (). authorizeRequests ()
.antMatchers ("/api/patients/**"). access ("hasRole ('PATIENT')")
.and (). requestMatchers (). antMatchers ("/api/doctors/**") // Deny access to "/ admin"
.and (). authorizeRequests ()
.antMatchers ("/api/doctors/**"). access ("hasRole ('DOCTOR')");
}
}
The CustomUserDetailsService class for reference if required:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UsersRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> usersOptional = userRepository.findByEmail(email);
Users user = null;
if(usersOptional.isPresent()) {
System.out.println(usersOptional.isPresent());
user = usersOptional.get();
}else {
throw new RuntimeException("Email is not registered!");
}
return new CustomUserDetails(user);
}
}
As I think, the server should only check for the validity of the refresh token as we don't pass the user details with refresh token. So I don't know why it requires the userDetails in the first place.
Please help and guide if I am missing something!
Thanks in advance.
I don't sure. But as I see your code in WebSecurityConfiguration could wired default InMemoryUserDetailsManager UserDetailsService .That could be reason why you have 2 different provider. In one you write, from the other you read users. Please try change your code as I show below and let me know if it help:
Was:
#Autowired
private UserDetailsService customUserDetailsService;
My vision how should be:
#Autowired
private CustomUserDetailsService customUserDetailsService;

Spring Boot OAuth doesn't return a refresh token for clients

I have an API that I've developed in Spring Boot, and I've just noticed that it's not returning a refresh token when you request an access token.
The response from the API looks like this;
{
"access_token": "ed0bdc62-dccf-4f58-933c-e28ad9598843",
"token_type": "bearer",
"expires_in": 2589494,
"scope": "read write"
}
My configuration looks like this;
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "myapi";
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/view/**").permitAll()
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore tokenStore;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
I previously had the project setup to use JWT for access tokens and that did return a refresh token, however I had to remove JWT as it wasn't compatible with using the token store.
To confirm, it returns a refresh token when the grant_type = password, but not when it's set to 'client_credentials'.
Does anyone have any suggestions why my configuration doesn't return a refresh token?
4.3.3. Access Token Response in RFC 6749 (The OAuth 2.0 Authorization Framework) says "A refresh token SHOULD NOT be included." Therefore, most implementations of OAuth 2.0 authorization servers do not generate a refresh token in Client Credentials flow.
I got the same issue, then I changed in this method, I added REFRESH_TOKEN then in the response I am getting refresh_token value.
static final String REFRESH_TOKEN = "refresh_token";
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(CLIENT_ID)
.secret(messageDigestPasswordEncoder.encode(CLIENT_SECRET))
.authorizedGrantTypes(GRANT_TYPE,REFRESH_TOKEN)
.scopes(SCOPE_READ, SCOPE_WRITE ,TRUST)
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
}

Spring Boot OAuth TokenStore is always empty

I'm trying to implement an OAuth Authentication Server with Spring Boot OAuth package. I was following this article, but instead of using JDBC TokenStore I only need InMemoryTokenStore and implicit grant for the start.
The application is secured with form login and I'm able to login, grant access and get redirected to with a short token to the redirect uri. But after that, the InMemoryTokenStore is empty. I tried debugging and it looks like the method storeAccessToken is never called.
My config file looks like this:
#Configuration
#EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
private Logger log = Logger.getLogger(getClass().getName());
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("tripapp")
.authorizedGrantTypes("implicit")
.redirectUris("http://localhost:8080/redirect")
.scopes("read")
.authorities("USER")
.autoApprove(false)
.accessTokenValiditySeconds(3600);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
Has anyone an idea, where the problem is?

Spring oauth2 - Cannot login to /oauth/authorization

I'm trying to secure my REST app with OAUTH2, in this case with Authorization Code Grant. So basically i think that when trying to access the /oauth/authorize endpoint, i should get a login page (from my app) where the user should authenticate with my app in order to make the client app get the token in the redirect_uri. Is this correct?
My clients are in a DB, I have created all the tables Spring Security needs. In this case I have a client_id = test and authorized_grant_types "access_token,refresh_token,implicit,authorization_code"
This is my AuthorizationServer config
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authManager);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
The resource server (in the same app) is
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").access("hasRole('USER')");
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
}
Last, my general SecurityConfiguration sets the security levels for the web app part of the application and not for the "/api/**" part. I'm trying with PostMan to send a post request to http://localhost:8080/oauth/authorize with this parameters: client_id=test redirect_uri=http://test.com response_type=code but i'm getting this error
User must be authenticated with Spring Security before authorization can be completed.
I thought i should get a login window where the user should authenticate
what i'm doing wrong?

Resources