I am trying to implement oauth2 with a jwt in spring boot and the autentication works but when I want to get the refresh_token an error occurs that indicates the following ...
java.lang.IllegalStateException: UserDetailsService is required.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:464)
at org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper.loadUserDetails(UserDetailsByNameServiceWrapper.java:68)
at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate(PreAuthenticatedAuthenticationProvider.java:103)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.refreshAccessToken(DefaultTokenServices.java:150)
What am I doing wrong?
These are my files
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${token.secret}")
private String secret;
#Value("${server.servlet.context-path}")
private String contextPath;
#Value("${oauth2.client.id}")
public String CLIENT_ID;
#Value("${oauth2.client.secret}")
public String CLIENT_SECRET;
#Value("${oauth2.scope.read}")
public String SCOPE_READ;
#Value("${oauth2.grant.types}")
public String GRANT_TYPES;
#Value("${oauth2.scopes}")
public String SCOPES;
#Value("${oauth2.access.token.validity}")
public Integer TOKEN_VALID_SECONDS;
#Value("${oauth2.refresh.token.validity}")
public Integer REFRESH_TOKEN_VALID_SECONDS;
#Autowired
private DataSource dataSource;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(CLIENT_ID)
.secret(passwordEncoder().encode(CLIENT_SECRET))
.authorizedGrantTypes("password", "refresh_token", "client_credentials","authorization_code")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(TOKEN_VALID_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALID_SECONDS);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
And this is my SecurityConfig class
#EnableGlobalMethodSecurity(securedEnabled=true)
#Configuration
#RequiredArgsConstructor
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private final CustomUserDetailsService customUserDetailsService;
#Autowired
public BCryptPasswordEncoder passwordEncoder;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/oauth/token/**").permitAll()
.antMatchers("/api/**" ).authenticated()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().csrf().disable();
}
}
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
//login web service url describe on properties file
#Value("${loginServiceUri}")
public String loginServiceUri;
//login web service enabled
#Value("${loginWebServiceEnabled}")
public Boolean loginWebServiceEnabled;
#Override
public UserDetails loadUserByUsername(String username) {
log.debug("Trying to authenticate user with details....");
if (loginWebServiceEnabled && loginServiceUri != null){
//its necessary to call external Web Service to find the user and then look for
//into database
UserDto userDto = this.loginWebService(username);
if (userDto != null) {
// look for the user in data base
log.debug("User found at external login web service trying to look for at data base");
return lookUserDataBase(userDto.getUsername());
} else {
log.error("User not found at external login web service", username);
throw new UsernameNotFoundException(username);
}
} else {
// look for the user in data base
return lookUserDataBase(username);
}
}
/**
* Look for use in data base by user name
* #return
*/
private UserDetails lookUserDataBase(String userName) {
UserEntity user = userService.findEntityByUsername(userName);
if (user == null) {
log.error("User not found in data base", userName);
throw new UsernameNotFoundException(userName);
}
log.debug("User found in data base with userName: " + userName + " and authorities: " + user.getUserAuthority().toString());
return new UserAuthenticatedDetails(user);
}
/**
* Example Login Web Service call login
* #param name
* #return
*/
private UserDto loginWebService (String name){
xxxxxxxxxxx
}
}
Add these in your WebSecurityConfigurerAdapter class
#Autowired
public void setApplicationContext(ApplicationContext context) {
super.setApplicationContext(context);
AuthenticationManagerBuilder globalAuthBuilder = context
.getBean(AuthenticationManagerBuilder.class);
try {
globalAuthBuilder.userDetailsService(userDetailsService);
} catch (Exception e) {
e.printStackTrace();
}
}
There is a "local" AuthenticationManagerBuilder and a "global" AuthenticationManagerBuilder and we need to set it on the global version in order to have this information passed to these other builder contexts.
Read more about it here
I solved with this lines in SecurityConfig;
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
//this line solved the problem
endpoints.userDetailsService(customUserDetailsService);**
}
Either you configure some inMemory user as follows:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER")
.and()
.withUser("manager")
.password("password")
.credentialsExpired(true)
.accountExpired(true)
.accountLocked(true)
.authorities("WRITE_PRIVILEGES", "READ_PRIVILEGES")
.roles("MANAGER");
}
or you implement a UserDetailService e.g. which looks up in the database for a user.
Related
Hope you all are doing good and safe. I have an application which will allow a user to login either by using his LDAP credentials or using his credentials stored on database. I was able to configure separate jdbc and ldap authenticator but how can I implement it conditionally? Say, a user selects LDAP radio button on login page then it should trigger LDAP authentication and if user selects database authentication, then it should trigger jdbc authentication. Any help would be highly appreciated.
JDBC authenticator:-
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.authorizeRequests()
.antMatchers("/admin").hasAnyRole("ADMIN")
.antMatchers("/user").hasAnyRole("ADMIN", "USER")
.antMatchers("/").permitAll()
.and().formLogin();
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
**LDAP authenticator:-**
#EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private UserDetailsService userDetailsService;
#Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler() {
String message = "";
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception.getClass().isAssignableFrom(UsernameNotFoundException.class)) {
message = "User Not Found";
} else if (exception.getClass().isAssignableFrom(DisabledException.class)) {
message = "Account Disabled";
} else if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
message = "Bad Credentials";
}
else if (exception.getClass().isAssignableFrom(LockedException.class)) {
message = "Account Locked";
}
else {
message = "Internal Server Error";
}
response.sendRedirect("/WisoKeyinPortal/login?error=" + message);
}
};
}
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
return authenticationProvider;
}
/*---------------------------For QA comment from here-------------------------------*/
#Bean public AuthenticationManager authenticationManager() { return new
ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean public AuthenticationProvider
activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new
ActiveDirectoryLdapAuthenticationProvider("xyz.com", "ldap://xyz.com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setSearchFilter("sAMAccountName={1}"); return provider; }
/*----------------------------For QA comment ends here-----------------------------*/
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] staticResources = { "/css/**", "/images/**", "/fonts/**", "/scripts/**", };
http.csrf().disable().authorizeRequests().antMatchers("/login**").permitAll().antMatchers(staticResources)
.permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll()
.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).and()
.logout().invalidateHttpSession(true).clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login").permitAll();
//.and().sessionManagement().invalidSessionUrl("/login?error=session");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
//authManagerBuilder.inMemoryAuthentication().withUser("admin").password("pass").roles("ADMIN");
/*---------------------------For QA comment from here-------------------------------*/
authManagerBuilder.authenticationProvider(
activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService());
/*---------------------------For QA comment from here-------------------------------*/
}
}
Im trying to implement Authorisation server with password and client_credentials grant
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
public PasswordEncoder passwordEncoder;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore jwtTokenStore;
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private TokenEnhancer jwtTokenEnhancer;
#Bean
public TokenEnhancer jwtTokenEnhancer(){
return new JWTokenEnhancer();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer(), jwtAccessTokenConverter));
endpoints
.authenticationManager(authenticationManager)
.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder)
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
}
web config file
Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userService")
private UserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/api-docs/**").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Allow eureka client to be accessed without authentication
web.ignoring().antMatchers("/*/")//
.antMatchers("/eureka/**")//
.antMatchers(HttpMethod.OPTIONS, "/**"); // Request type options should be allowed.
}
}
#Configuration
public class JwtTokenConfig {
#Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("dev");
return accessTokenConverter;
}
}
i have configured client details to pick up from database -
When i try to get access token based on password grant im able to get the access token
but when i try to get access token based on grnat_type client credentials - im getting internal server error .
Please help to check on what is wrong with my implementation.
enter image description here
In your class OAuthConfiguration, check client configuration present in configure(ClientDetailsServiceConfigurer clients) method. It appears that the JDBC client details service is not able to find any client details.
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.listClientDetails(); // This probably would be empty.
If so, configure JDBC client details service something like this:
clients.jdbc(dataSource).withClient(CLIEN_ID)
.secret(encoder.encode(CLIENT_SECRET))
.authorizedGrantTypes("password", "refresh_token", "client_credentials")
.scopes("read", "write")
.resourceIds(RESOURCE_ID).and().build();
Found the Issue .
public class JWTokenEnhancer implements TokenEnhancer{
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
info.put("user-info", "user additional information...");
// User user = (User) authentication.getPrincipal();
// info.put("isAdmin", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()).contains("ROLE_ADMIN"));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
The commented line was causing the issue as there was no user in case of client_credentials
.antMatchers("/secure2/**").authenticated();
I have configured one of my api as protected, when I try to access it, It gives me Access Denied error message, I do not know what could be the reason. Note that I am passing valid access token.
My scenario:
Basically I have created logout rest api in authorization server and I want that, request with valid token is allowed to hit this api.
Request:
GET /auth/secure2 HTTP/1.1
Host: localhost:9191
Authorization: Bearer 33984141-1249-4465-a3aa-0b95a053fc63
Cache-Control: no-cache
Postman-Token: f4661790-a8e1-90ea-f6db-79cb37958cdf
Response:
{
"timestamp": 1500186837033,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/auth/secure2"
}
I found out that below method return false and due to that it raise the access denied error.
public final class ExpressionUtils {
public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
try {
return ((Boolean) expr.getValue(ctx, Boolean.class)).booleanValue();
}
catch (EvaluationException e) {
throw new IllegalArgumentException("Failed to evaluate expression '"
+ expr.getExpressionString() + "'", e);
}
}
}
Below are the screen shots which I captured by debugging in framework. Please also check the images mentioned in comment.
Code:
SecurityConfiguration.java :
import org.springframework.beans.factory.annotation.Autowired;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public ShaPasswordEncoder encoder() {
return new ShaPasswordEncoder(256);
}
#Override
public void configure(WebSecurity web) throws Exception {}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/hello/").permitAll()
.antMatchers("/secure3/").permitAll()
.antMatchers("/oauth/token/revoke/**").authenticated()
.antMatchers("/secure2/**").authenticated();
}
#Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
public GlobalSecurityConfiguration() {
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
}
Oauth2Configuration.java
#Configuration
public class OAuth2Configuration {
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
//private static final String PROP_CLIENTID = "clientid";
//private static final String PROP_SECRET = "secret";
private static final String PROP_ACCESS_TOKEN_VALIDITY_SECONDS = "accessTokenValidityInSeconds";
private static final String PROP_REFRESH_TOKEN_VALIDITY_SECONDS = "refreshTokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailService userDetailsService;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.tokenStore(tokenStore())
.userDetailsService(userDetailsService)
.tokenEnhancer(tokenEnhancer())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("clientId")
.scopes("read", "write")
.authorities(Authorities.ROLE_ADMIN.name(), Authorities.ROLE_USER.name())
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_ACCESS_TOKEN_VALIDITY_SECONDS, Integer.class, 80))
.refreshTokenValiditySeconds(propertyResolver.getProperty(PROP_REFRESH_TOKEN_VALIDITY_SECONDS, Integer.class, 180))
.and().inMemory()
.withClient("clientid")
.scopes("read", "write")
.authorities(Authorities.ROLE_ADMIN.name(), Authorities.ROLE_USER.name())
.authorizedGrantTypes("client_credentials")
.secret("secret");
}
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
Controller:
#Controller
#RequestMapping("/secure2")
public class SecureController1 {
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public String sayHello() {
return "Secure Hello secure2!";
}
}
What are the scenarios in which it raises access denied error message? Please let me know if any other information in needed.
I used these codes and they worked well.
OAuth2AuthorizationServerConfig.java:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
// Configure the token store and authentication manager
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//#formatter:off
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter()) // added for JWT
.authenticationManager(authenticationManager);
//#formatter:on
}
// Configure a client store. In-memory for simplicity, but consider other
// options for real apps.
//It is not necessary.works even without this func:)
// #Override
// public void configure(AuthorizationServerSecurityConfigurer oauthServer)
// throws Exception {
// oauthServer
// .tokenKeyAccess("permitAll()")
// .checkTokenAccess("isAuthenticated()");
// }
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//#formatter:off
clients
.inMemory()
.withClient("myclient")//username in basic auth header
.secret ("{noop}123")//password in basic auth header;
.authorizedGrantTypes("authorization_code", "implicit", "password", "client_credentials", "refresh_token")
.scopes("read")
//.redirectUris("http://localhost:9191/x")
.accessTokenValiditySeconds(86400); // 24 hours
//#formatter:on
}
// A token store bean. JWT token store
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter()); // For JWT. Use in-memory, jdbc, or other if not JWT
}
// Token converter. Needed for JWT
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123"); // symmetric key
return converter;
}
// Token services. Needed for JWT
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
// #Bean
//
// public PasswordEncoder passwordEncoder () {
//
// return new BCryptPasswordEncoder();
//
// }
}
WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
//It is not important to use this func. /oauth/token is the default path of spring security to use oauth2. she is so clever!! :)
// #Override
// protected void configure(HttpSecurity http) throws Exception {
//
//
// http.authorizeRequests()
// .antMatchers(HttpMethod.POST, "/oauth/token").permitAll()
// .anyRequest().authenticated();
// }
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}user").roles("ROLE");
}
//
// #Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService)
// .passwordEncoder(passwordEncoder());
// }
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//
// #Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
}
Output:
Please notice that the values of username and password used in code and postman, must be the same.
enter image description here
enter image description here
Hope to be helpful:)
I am trying to configure the spring security for my application. The authentication is up and running, and I am able to generate the oauth tokens using the oauth/token url. Now when I use this token I am getting the error
17:47:08,668 DEBUG SessionManagementFilter:124 - Requested session ID Lna1JBtS5foU2qDaGONIzBcGgvt94FTSneANgG77 is invalid.
17:47:08,670 DEBUG FilterSecurityInterceptor:219 - Secure object: FilterInvocation: URL: /api/user/update; Attributes: [hasAnyRole('ROLE_ANONYMOUS, USER')]
17:47:08,671 DEBUG ExceptionTranslationFilter:164 - Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:223)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
Below are my configuration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
private MyAuthenticationProvider myAuthenticationProvider;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token", "/api/signup").permitAll()
.anyRequest().hasAnyRole("ANONYMOUS, USER");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/signup");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
public TokenStoreUserApprovalHandler userApprovalHandler() {
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore());
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore());
return store;
}
}
AuthorizationServer class
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static String REALM = "ABCDEF";
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("user").secret("secret")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT").scopes("read", "write", "trust") //
.accessTokenValiditySeconds(60 * 60 * 24 * 1) // Access token is only valid for 1 days.
.refreshTokenValiditySeconds(60 * 60 * 24 * 30); // Refresh token is only valid for 30 days.
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancer()).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").realm(REALM);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new MicroInvestTokenEnhancer();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123456789");
return converter;
}
}
Authentication Provider
#Component("myAuthenticationProvider")
public class MyAuthenticationProvider implements AuthenticationProvider {
#Autowired
private LoginService loginService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MicroInvestAuthenticationToken auth = null;
if (authentication != null) {
final String username = authentication.getPrincipal().toString();
final String password = authentication.getCredentials().toString();
LoginResponse user = loginService.login(username, password);
if (user != null) {
final List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
auth = new MicroInvestAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), grantedAuthorities);
auth.setUser(user);
}
}
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class).isAssignableFrom(authentication);
}
}
I use mongo custom tokenStore and codeService:
this is my custom mongoTokenStore:
public class MongoTokenStore implements TokenStore {
private final MongoAccessTokenRepository mongoAccessTokenRepository;
private final MongoRefreshTokenRepository mongoRefreshTokenRepository;
private AuthenticationKeyGenerator authenticationKeyGenerator =
new DefaultAuthenticationKeyGenerator();
public MongoTokenStore(MongoAccessTokenRepository mongoAccessTokenRepository, MongoRefreshTokenRepository mongoRefreshTokenRepository) {
this.mongoAccessTokenRepository = mongoAccessTokenRepository;
this.mongoRefreshTokenRepository = mongoRefreshTokenRepository;
}
#Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken oAuth2AccessToken) {
return readAuthentication(oAuth2AccessToken.getValue());
}
#Override
public OAuth2Authentication readAuthentication(String tokenId) {
return mongoAccessTokenRepository.findByTokenId(tokenId)
.map(MongoAccessToken::getAuthentication)
.orElse(null);
}
#Override
public void storeAccessToken(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
MongoAccessToken mongoAccessToken = new MongoAccessToken(oAuth2AccessToken, oAuth2Authentication,
authenticationKeyGenerator.extractKey(oAuth2Authentication));
mongoAccessTokenRepository.save(mongoAccessToken);
}
#Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
return mongoAccessTokenRepository.findByTokenId(tokenValue)
.map(MongoAccessToken::getOAuth2AccessToken)
.orElse(null);
}
#Override
public void removeAccessToken(OAuth2AccessToken oAuth2AccessToken) {
mongoAccessTokenRepository.findByTokenId(oAuth2AccessToken.getValue())
.ifPresent(mongoAccessTokenRepository::delete);
}
#Override
public void storeRefreshToken(OAuth2RefreshToken oAuth2RefreshToken, OAuth2Authentication oAuth2Authentication) {
MongoRefreshToken token=new MongoRefreshToken(oAuth2RefreshToken,oAuth2Authentication);
mongoRefreshTokenRepository.save(token);
}
#Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
return mongoRefreshTokenRepository.findByTokenId(tokenValue)
.map(MongoRefreshToken::getOAuth2RefreshToken)
.orElse(null);
}
#Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken oAuth2RefreshToken) {
return mongoRefreshTokenRepository.findByTokenId(oAuth2RefreshToken.getValue())
.map(MongoRefreshToken::getAuthentication)
.orElse(null);
}
#Override
public void removeRefreshToken(OAuth2RefreshToken oAuth2RefreshToken) {
mongoRefreshTokenRepository.findByTokenId(oAuth2RefreshToken.getValue())
.ifPresent(mongoRefreshTokenRepository::delete);
}
#Override
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken oAuth2RefreshToken) {
mongoAccessTokenRepository.findByRefreshToken(oAuth2RefreshToken.getValue())
.ifPresent(mongoAccessTokenRepository::delete);
}
#Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication oAuth2Authentication) {
return mongoAccessTokenRepository.findByAuthenticationId(authenticationKeyGenerator
.extractKey(oAuth2Authentication))
.map(MongoAccessToken::getOAuth2AccessToken)
.orElse(null);
}
#Override
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String s, String s1) {
return mongoAccessTokenRepository.findByClientIdAndUserName(s,s1)
.stream()
.map(MongoAccessToken::getOAuth2AccessToken)
.collect(Collectors.toList());
}
#Override
public Collection<OAuth2AccessToken> findTokensByClientId(String s) {
return mongoAccessTokenRepository.findByClientId(s)
.stream()
.map(MongoAccessToken::getOAuth2AccessToken)
.collect(Collectors.toList());
}
}
and this is my custom mongoCodeService:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class MongoAuthenticationCodeService extends RandomValueAuthorizationCodeServices{
private final MongoAuthenticationCodeRepository repository;
#Override
protected void store(String code, OAuth2Authentication oAuth2Authentication) {
repository.save(new MongoAuthenticationCode(code,oAuth2Authentication));
}
#Override
protected OAuth2Authentication remove(String code) {
return repository.findOneByCode(code)
.map(MongoAuthenticationCode::getAuthentication)
.orElse(null);
}
}
and my OAuth2Config:
#Configuration
public class OAuth2ServerConfiguration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(READ_AND_WRITE_RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/webjars/**", "/oauth/authorize/**", "/", "/customLogout",
"/oauth/check_token/**", "/login").permitAll()
.mvcMatchers(HttpMethod.GET, "/loginAttemptUsers").hasRole("ADMIN")
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailService customUserDetailService;
#Autowired
private MongoAuthenticationCodeService mongoAuthenticationCodeService;
#Autowired
private MongoTokenStore mongoTokenStore;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.authorizationCodeServices(mongoAuthenticationCodeService)
.tokenStore(mongoTokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(customUserDetailService)
.approvalStoreDisabled();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("kksdi2388wmkwe")
.authorizedGrantTypes("authorization_code", "password", "refresh_token")
.scopes("read", "write")
.resourceIds("ReadAndWriteResource")
.secret("kksd23isdmsisdi2")
.autoApprove(true)
.accessTokenValiditySeconds(120)
.refreshTokenValiditySeconds(1200);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore);
return tokenServices;
}
}
}
the problem is:
I can login from spring zuul ui-server by "authorization_code",and can
access ResourceServer data .
But when the access_token expired(120s), while re-getting ResourceServer data, I see mongoTokenStore removed the existed access_token , but why not refresh new access_token automatically?
How to resolve it?
The whole point of refresh token is to add one more security dimension to OAuth2.
When the end user has acquired access token, he now can access any resource within the authorization rules that the token awards him.
If his access token is stolen somehow, the attacker can now access to any resource that the access token authorizes him, but if we set an expiry time for the access token the attacker will have limited access time.
With that being said, if spring oauth2 would have refresh that token automatically that one more security aspect would not be applied, and the whole idea of refreshing the token would be wasted.
So, to conclude it's your responsibility to make sure that your end user is re-authorize via OAuth2 again.
You can also read this:
Why Does OAuth v2 Have Both Access and Refresh Tokens?