Web and Mobile Clients for Spring Security OAuth2

Maksim picture Maksim · Aug 12, 2015 · Viewed 7.6k times · Source

I'm trying to wrap my head around OAuth2 and Spring Security OAuth, especially OAuth Provider service. I'm trying to implement the following:

  1. OAuth Provider
  2. Resource Server (RESTful webservices that should be protected using OAuth Provider (1))
  3. Web Client (a web client application that is secured using Spring Security but should use OAuth Provider (1) to authenticate user
  4. Native Mobile Clients (Android and iOS) that should as well use OAuth Provider (1) for authentication

All these modules are independent from each other, i.e. separated in different projects and will be hosted on different domains, such as (1) http://oauth.web.com, (2) http://rest.web.com, (3) http://web.com

My two questions are:

A. How do I implement a Web Client project so that when user is logging in on the protected page or clicks on the Login button, be redirected to OAuth Provider url, login, and be authenticated on the Web Client with all user roles and also as well need to know which client was used. @EnableResourceServer (the same way Resource Server is implemented; see code below) in this project to get user's details? Do I have to manage Access Token and always include it in the call to the Resource Server or it can be done somehow automatically?

B. What is the best way to implement security on the mobile apps that I'll be developing. Should I be using password grand for this authentication, since the apps will be build by me where I'll have a user name and password be in the native screen and then be send to the server as a basic authentication over SSL? Are there any samples that I can take a look at that talk to Spring Security OAuth and return user details.

Here is my implementation of OAuth Project (1) and Resource Project (2):

1. OAuth Provider

OAuth2 Server Configs (most of the code was taken from HERE)

@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Autowired
    DataSource dataSource;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore())
                .approvalStore(approvalStore())
                .authorizationCodeServices(authorizationCodeServices())
        ;
    }

    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {

        oauthServer.checkTokenAccess("permitAll()");
    }
}

Web Security Config

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable(); // TODO. Enable this!!!

        http.authorizeRequests()
                .and()
                .formLogin()
//                .loginPage("/login") // manually defining page to login
//                .failureUrl("/login?error") // manually defining page for login error
                .usernameParameter("email")
                .permitAll()

                .and()
                .logout()
//                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .permitAll();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(customUserDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

UserDetailsService (customUserDetailsService)

@Service
public class CustomUserDetailsService implements UserDetailsService{

    private final UserService userService;

    @Autowired
    public CustomUserDetailsService(UserService userService) {
        this.userService = userService;
    }

    public Authority loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userService.getByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=%s was not found", email)));
        return new Authority(user);
    }
}

2. Resource Server (RESTful WS)

Configuration (most of the skeleton code was take from THIS example)

@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter{

    @Autowired
    DataSource dataSource;

    String RESOURCE_ID = "data_resource";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        TokenStore tokenStore = new JdbcTokenStore(dataSource);
        resources
                .resourceId(RESOURCE_ID)
                .tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // For some reason we cant just "permitAll" OPTIONS requests which are needed for CORS support. Spring Security
                // will respond with an HTTP 401 nonetheless.
                // So we just put all other requests types under OAuth control and exclude OPTIONS.
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                .and()

                // Add headers required for CORS requests.
                .headers().addHeaderWriter((request, response) -> {
            response.addHeader("Access-Control-Allow-Origin", "*");

            if (request.getMethod().equals("OPTIONS")) {
                response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
                response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
            }
        });    
    }
}

WS Controller:

@RestController
@RequestMapping(value = "/todos")
public class TodoController {

    @Autowired
    private TodoRepository todoRepository;

    @RequestMapping(method = RequestMethod.GET)
    public List<Todo> todos() {
        return todoRepository.findAll();
    }

   // other methods
}

Answer

MangEngkus picture MangEngkus · Aug 16, 2015

How do I implement a Web Client project so that when user is logging in on the protected page or clicks on the Login button, be redirected to OAuth Provider url, login, and be authenticated on the Web Client with all user roles and also as well need to know which client was used

You want to use OAuth as SSO.

Option 1, use spring cloud https://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v

Option 2, manually handle SSO process:

in your web client, configure the login page to OAuth server using authorization grant.

protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable(); // TODO. Enable this!!!
    http.authorizeRequests()
    .and()
    .formLogin()
    .loginPage("http://oauth.web.com/oauth/authorize?response_type=code&client_id=webclient&redirect_uri=http://web.com") // manually defining page to login
    //.failureUrl("/login?error") // manually defining page for login error
    .usernameParameter("email")
    .permitAll()   
    .and()
    .logout()
    //.logoutUrl("/logout")
    .logoutSuccessUrl("/")
    .permitAll();
}

after authentication and authorization process finished, you will be redirected to web client with authorization code http://web.com/?code=jYWioI. your web client should exchange this code with token access on your oauth server. On your oauth server, create an endpoint for retrieving user information

@RestController
public class UserRestService {

  @RequestMapping("/user")
  public Principal user(Principal user) {
    // you can also return User object with it's roles
    // {"details":...,"principal":{"username":"user",...},"name":"user"}
    return user;
  }

}

Then, your web client can access the user details information by sending request with the token access to the above rest endpoint and authenticate the user based on the response.

Do I have to manage Access Token and always include it in the call to the Resource Server or it can be done somehow automatically?

Every request must include token access. If you want to do it automatically, spring has provide Oauth 2 client http://projects.spring.io/spring-security-oauth/docs/oauth2.html

What is the best way to implement security on the mobile apps that I'll be developing. Should I be using password grand for this authentication, since the apps will be build by me where I'll have a user name and password be in the native screen and then be send to the server as a basic authentication over SSL?

Since you are using native screen, password grant is enough, but you can store the refresh token, this will enable you to request token access without repeating the authentication process.

Are there any samples that I can take a look at that talk to Spring Security OAuth and return user details.

see sample code above or take a look at this https://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v