Spring Boot OAuth2/Cognito: Get UserInfo by AccessToken - java

I have a spring boot resource server that authenticates the user by the accessToken extracted from a cookie. The accessToken is retrieved from Cognito UserPool in a react FE and written to a cookie. It seems spring managed to authenticate the user and I can see username in SecurityContextHolder.getContext().authentication.name. I need to retrieve the rest of user attributes, like email. Most of the solutions I looked up say the SecurityContextHolder.getContext().authentication.principal should contain all attributes I need. Its a string in my case and I can't cast it to any User object. Even SecurityContextHolder.getContext().authentication.details is null.
I have user-info-uri defined in my application-properties security.oauth2.resource.user-info-uri. I feel I missing something that causes user attributes to be missing from the authentication context.
This is my resource server security configuration:
#Configuration
#EnableResourceServer
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends ResourceServerConfigurerAdapter {
private final ResourceServerProperties resource;
public SpringSecurityConfig(ResourceServerProperties resource) {
this.resource = resource;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenExtractor(new CustomExtractor());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests();
}
// Enabling Cognito Converter
#Bean
public TokenStore jwkTokenStore() {
return new JwkTokenStore(
Collections.singletonList(resource.getJwk().getKeySetUri()),
new CognitoAccessTokenConverter(),
null);
}
}

Related

Updated password not picked up after InMemoryUserDetailsManager updateUser call

Hi I have a Rest WS using WebSecurityConfigurerAdapter to implement HTTP Basic auth.
The password is allowed to be updated and I need to let the WS to pick up updated password without restarting server
Following are the codes:
SecurityConfig
// init a user with credentials admin/password
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//disable csrf
.csrf().disable()
//authentic all requests
.authorizeRequests().anyRequest().authenticated().and().httpBasic()
//disable session
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager());
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
Properties users = new Properties();
users.put("admin", "password,USER,enabled");
return new InMemoryUserDetailsManager(users);
}
}
The controller that will update password
#RestController
public class someController{
#Autowired
public InMemoryUserDetailsManager inMemoryUserDetailsManager;
// update password from password -> pass
#RequestMapping(...)
public updatePass(){
ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
grantedAuthoritiesList.add(new SimpleGrantedAuthority("USER"));
this.inMemoryUserDetailsManager.updateUser(new User("admin", "pass", grantedAuthoritiesList));
}
// another way that also doesn’t work
#RequestMapping(...)
public newUpdate(){
ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
grantedAuthoritiesList.add(new SimpleGrantedAuthority("USER"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("admin", "pass",
grantedAuthoritiesList);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
}
}
After calling updatePass() with credential admin/password for the first time, I can see that the password has been updated to "pass" in debugger
I assume that if I'm to call updatePass() again, I should use admin/pass. However it turned out to be still using the old admin/password.
Sources I referred to when writing this code source1 source2
*I'm using Advance Rest Client to make the calls
When you update the password, you have to set the UserDetails in springSecurityContext object if the user is authenticated.
instead of using SecurityContext, I overwrote function loadUserByUsername of interface UserDetailsService to let spring security always pick up the latest pwd from DB.

Spring Security log in working locally but not on live site

My application is structured with Java and Spring Security. I have two ways to log in, one is using windows log in (aka active directory), the other is using a password created by an admin user. Both of these are working when I run locally through IntelliJ but when I deploy the app to the server, it only lets people using windows login credentials log in. If you try to log in with a custom made login on the live site, it just refreshes the login page and removes the entered username and password. I am not getting any errors. Also, when a user admin creates the custom log in, it is stored in a sql table, with the password hashed. So the create function works/user exists, just logging in is not working. Any idea what might be the cause... Not sure if I should be looking at the code or the server connection. I use Tomcat and IIS to host this site.
Web Config File
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${ad.hg.url}")
private String AD_HG_URL;
#Value("${ad.hp.nt.url}")
private String AD_HP_NT_URL;
#Autowired
DBAuthorizationFetcher dbAuthorizationFetcher;
#Autowired
ManualUserDetailsService manualUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/specialSplit/**");
http.authorizeRequests()
.antMatchers("/css/**","/js/**","/images/**","/login","/accessDenied","/loginFailed","/changePassword","/resetPassword").permitAll()
.antMatchers("/newClient","/callLogs/**","/addClient","/saveClient","/delete/**","/save/**","/specialSplit/**").hasRole("OLIDB_ADMIN")
.antMatchers("/admin","/toggle/user/**").hasRole("USER_ADMIN")
.anyRequest().hasRole("OLIDB_USER").and()
.formLogin().loginPage("/login").failureHandler(new CustomAuthenticationFailureHandler()).successForwardUrl("/")
.and().exceptionHandling().accessDeniedPage("/accessDenied")
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
//authManagerBuilder.authenticationProvider(databaseAuthenticationProvider);
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider("HEFFGROUP.COM",AD_HP_NT_URL));
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider("HG",AD_HG_URL));
authManagerBuilder.authenticationProvider(manualAuthenticationProvider());
}
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider(String domain,String url) {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(domain, url);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(dbAuthorizationFetcher);
return provider;
}
public DaoAuthenticationProvider manualAuthenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(manualUserDetailsService);
authProvider.setPasswordEncoder(new BCryptPasswordEncoder(11));
return authProvider;
}
}

Spring Boot 2.0.3 Oauth2 Security: Getting 401 error even when using access token in header

I'm creating a spring boot 2.0 application and trying to enable oauth2 security. I have Auth server and Resource server in the same application as of now. My client and user details as well as token generated are persisted in databases (mysql) and database schema is the same as provided by spring documentation. When I hit the '/oauth/token/' endpoint providing clientId and clientSecret in header and user's credentials in body using Postman, I'm getting access token successfully.
{
"access_token": "bef2d974-2e7d-4bf0-849d-d80e8021dc50",
"token_type": "bearer",
"refresh_token": "32ed6252-e7ee-442c-b6f9-d83b0511fcff",
"expires_in": 6345,
"scope": "read write trust"
}
But when I try to hit my rest api using this access token, I'm getting 401 Unauthorized error:
{
"timestamp": "2018-08-13T11:17:19.813+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/myapp/api/unsecure"
}
The rest APIs I'm hitting are as follows:
http://localhost:8080/myapp/api/unsecure
http://localhost:8080/myapp/api/secure
myapp is the context path of my application.
For 'secure' api, I have provided access token in request header as described in Spring documentation:
Authorization: Bearer bef2d974-2e7d-4bf0-849d-d80e8021dc50
Whereas for unsecure api, I have tried with and without Authentication header. In all cases I'm getting same error for both apis.
Also when I try to print currently authenticated user, its getting printed as anonymousUser.
What I want are as follows:
1) I want my secure api to be accessible only when access token is provided in request header.
2) I want my unsecure api to be accessible by unauthorised user.
3) I should get currently authenticated user using SecurityContextHolder when accessing secure url.
My WebSecurityConfigurerAdapter is as follows:
#Configuration
#EnableWebSecurity(debug=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
UserDetailsService userDetailsService;
#Autowired
private ClientDetailsService clientDetailsService;
#Bean
public PasswordEncoder userPasswordEncoder() {
return new BCryptPasswordEncoder(8);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws
Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(userPasswordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super.authenticationManagerBean();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore
tokenStore){
TokenStoreUserApprovalHandler handler = new
TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new
DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws
Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/index.html", "/**.js", "/**.css", "/").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
Here using antMatchers I have permitted static pages of Angular 6 application, as I'm planning to use those in my real app. And no, the following line does not work to allow static pages of angular application:
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
My AuthorizationServerConfigurerAdapter is as follows:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
My ResourceServerConfigurerAdapter is as follows:
#Configuration
#EnableResourceServer
public abstract class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server-rest-api";
#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()
.cors().disable()
.anonymous().disable()
.requestMatchers()
.antMatchers("/api/**").and()
.authorizeRequests()
.antMatchers("/api/secure").authenticated()
.antMatchers("/api/unsecure").permitAll();
}
}
But when I enable anonymous access in SecurityConfig and declare my unsecure url as permitAll, then I'm able to access that url.
.antMatchers("/api/unsecure", "/index.html", "/**.js", "/**.css", "/").permitAll()
My Controller class is as follows:
#RestController
#RequestMapping("/api")
public class DemoController {
#GetMapping("/secure")
public void sayHelloFriend() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Current User: "+authentication.getName());
System.out.println("Hello Friend");
}
#GetMapping("/unsecure")
public void sayHelloStranger() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Current User: "+authentication.getName());
System.out.println("Hello Stranger");
}
}
Let me know if any more information is needed. Any help will be appreciated. But please keep in mind that its Spring Boot 2.0 not 1.5 as both have some critical differences as per my findings.
Try to added
#Order(SecurityProperties.BASIC_AUTH_ORDER)
for the securityConfig? so the chain will check your Resource server's config first.
And not sure if that your type error, remove the abstract from the resource server.

Spring boot HTTP Basic pass through to JDBC

I have configured a JDBC data source and autowired the JDBCTemplate to execute custom SQL queries. I also have a simple HTTP Basic authentication:
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
However, I would like to use the user and password used for HTTP Basic to authenticate the user to the data base itself, i.e pass through the credentials of HTTP Basic to the data source and execute queries as the user who logged in with HTTP Basic authentication. I'm facing two issues here, one is that the username and password are in the application.properties file that I want to override every time a user authenticates and also (reload?) execute queries as that user instead of the ones specified in the properties file.
Update 1:
I could programmatically use username and password like below:
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.username("")
.password("")
.url("")
.driverClassName("")
.build();
}
But how to call this every time a user logs with the HTTP Basic auth with those credentials?
Use UserCredentialsDataSourceAdapter as #"M. Deinum" have suggested with some kind of filter or handling AuthenticationSuccessEvent.
Basically you should just call setCredentialsForCurrentThread method with current principal username and password.
You'll have to disable credential erasure for authentication manager in order to be able to retrieve user password after authentication.
#EnableWebSecurity
public static class Security extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false) // for password retrieving
.inMemoryAuthentication()
.withUser("postgres").password("postgres1").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().mvcMatchers("/").fullyAuthenticated();
}
}
Datasource adapter:
#Bean
public UserCredentialsDataSourceAdapter dataSource(DataSourceProperties properties) {
final UserCredentialsDataSourceAdapter dataSourceAdapter = new UserCredentialsDataSourceAdapter();
dataSourceAdapter.setTargetDataSource(DataSourceBuilder.create()
.driverClassName(properties.getDriverClassName())
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.type(SimpleDriverDataSource.class) // disable pooling
.build());
((SimpleDriverDataSource) dataSourceAdapter.getTargetDataSource()).setDriverClass(org.postgresql.Driver.class); //binder won't set it automatically
return dataSourceAdapter;
}
AuthenticationSuccessHandler:
#Component
public static class AuthenticationHandler /*implements ApplicationListener<AuthenticationSuccessEvent> use that if your spring version is less than 4.2*/ {
private final UserCredentialsDataSourceAdapter dataSourceAdapter;
#Autowired
public AuthenticationHandler(UserCredentialsDataSourceAdapter dataSourceAdapter) {
this.dataSourceAdapter = dataSourceAdapter;
}
#EventListener(classes = AuthenticationSuccessEvent.class)
public void authenticationSuccess(AuthenticationSuccessEvent event) {
final Authentication authentication = event.getAuthentication();
final User user = (User) authentication.getPrincipal();
dataSourceAdapter.setCredentialsForCurrentThread(user.getUsername(), user.getPassword()); // <- the most important part
}
}
Or you can use Filter instead of event listener:
#Component
public static class DataSourceCredentialsFilter extends GenericFilterBean {
private final UserCredentialsDataSourceAdapter dataSourceAdapter;
#Autowired
public DataSourceCredentialsFilter(UserCredentialsDataSourceAdapter dataSourceAdapter) {
this.dataSourceAdapter = dataSourceAdapter;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final User user = (User) authentication.getPrincipal();
dataSourceAdapter.setCredentialsForCurrentThread(user.getUsername(), user.getPassword());
chain.doFilter(request, response);
dataSourceAdapter.removeCredentialsFromCurrentThread();
}
}
See full example here.

Using WebSecurityConfigurerAdapter with Spring OAuth2 and user-info-uri

I have created an Authorization service as follows
#SpringBootApplication
#EnableAuthorizationServer
public class AuthorizationApplication {
...
}
With this application.properties.
server.port=9000
security.oauth2.client.client-id=monederobingo
security.oauth2.client.client-secret=monederobingosecret
security.oauth2.client.authorized-grant-types=authorization_code,refresh_token,password,client_credentials
security.oauth2.client.scope=company,client
Then, in a separate spring boot project I have created a Resource Server.
#SpringBootApplication
#EnableResourceServer
public class App {
...
}
With this application.properties.
server.port=9090
spring.application.name=app
security.oauth2.resource.user-info-uri=http://localhost:9000/user
Now, everything works fine if I send a request like this localhost:9090/api with the appropriate token that was retrieved by Authorization Service.
However, I don't want to send this token when sending requests to localhost:9090/login.
For this I have created this class in my Resource server spring boot app.
#Configuration
public class SpringConfig extends WebSecurityConfigurerAdapter {
#Override protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login")
.permitAll()
.antMatchers("/api/**")
.authenticated();
}
}
And now I don't need to send any token to send a request to /login.
However, I'm now geting the following message when sending request to /api with a valid token.
{
"timestamp": 1496027102659,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/v1/points_configuration/314"
}
How can configure security for only a few URL patterns in Spring Security OAuth2?
Kindly follow this for more info regarding Spring OAuth security:Secure Spring REST Api with OAuth
In order to implement OAuth Security in Spring boot, you have to create Authorization & Resource server by extending them from AuthorizationServerConfigurerAdapter and ResourceServerConfigurerAdapter respectively.
Authorization Server
#Configuration
#EnableAuthorizationServer
public class AuthorizationApplication extends AuthorizationServerConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.userDetailsService(userDetailsService)
.authenticationManager(this.authenticationManager).tokenStore(tokenStore()).approvalStoreDisabled();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(mongoClientDetailsService);
/*inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities("ROLE_CLIENT")
.authorizedGrantTypes("password", "refresh_token","client_credentials")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 18000));*/
}
//Do others stuff
}
Resource Server
All the Url that you want to protect using OAuth should be mentioned in this server configuration. It enables a Spring Security filter that authenticates requests using an incoming OAuth2 token. While mostly WebSecurityConfigurerAdapter extended class is used for basic security configuration like adding filters, allowing un-secure url or implementing session policies etc.
#Configuration
#EnableResourceServer
public class App extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/api/**").and().authorizeRequests()
.antMatchers("/api/**").authenticated();
}
//Do others stuff
}

Categories