I noticed that my ResponseEntityExceptionHandler does not work with exceptions thrown by Spring Security in my Spring Boot application.
However, I need a way to catch InvalidGrantException which seems to get thrown when a user account is still disabled.
The use case is simple: If a user is currently disabled, I want to throw an appropriate error to my client s.t. it can display a message accordingly.
Right now the response by Spring Security is
{
error: "invalid_grant",
error_description: "User is disabled"
}
I saw this question but for some reason my AuthFailureHandler is not getting invoked:
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
#Component
public class AuthFailureHandler implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// Never reached ..
System.out.println("Hello World!");
}
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthEntryPoint());;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(customAuthEntryPoint());
}
#Bean
public AuthenticationEntryPoint customAuthEntryPoint(){
return new AuthFailureHandler();
}
}
Any idea what I am missing?
Configuration Code
Here is my OAuth2Configuration which also contains a ResourceServerConfiguraerAdapter which is supposed to handle the exception
#Configuration
#EnableAuthorizationServer
#EnableResourceServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
private final TokenStore tokenStore;
private final JwtAccessTokenConverter accessTokenConverter;
private final AuthenticationManager authenticationManager;
#Autowired
public OAuth2Configuration(TokenStore tokenStore, JwtAccessTokenConverter accessTokenConverter, AuthenticationManager authenticationManager) {
this.tokenStore = tokenStore;
this.accessTokenConverter = accessTokenConverter;
this.authenticationManager = authenticationManager;
}
#Value("${security.jwt.client-id}")
private String clientId;
#Value("${security.jwt.client-secret}")
private String clientSecret;
#Value("${security.jwt.scope-read}")
private String scopeRead;
#Value("${security.jwt.scope-write}")
private String scopeWrite;
#Value("${security.jwt.resource-ids}")
private String resourceIds;
private final static String WEBHOOK_ENDPOINTS = "/r/api/*/webhooks/**";
private final static String PUBLIC_ENDPOINTS = "/r/api/*/public/**";
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(clientId)
.secret(clientSecret)
.scopes(scopeRead, scopeWrite)
.resourceIds(resourceIds)
.accessTokenValiditySeconds(60*60*24*7 * 2) // Access tokens last two weeks
.refreshTokenValiditySeconds(60*60*24*7 * 12) // Refresh tokens last 12 weeks
//.accessTokenValiditySeconds(5)
//.refreshTokenValiditySeconds(10)
.authorizedGrantTypes("password", "refresh_token"); //, "client_credentials");
}
/**
* Since there are currently multiply clients, we map the OAuth2 endpoints under /api
* to avoid conflicts with #{#link ForwardController}
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Collections.singletonList(accessTokenConverter));
endpoints.tokenStore(tokenStore)
// Create path mappings to avoid conflicts with forwarding controller
.pathMapping("/oauth/authorize", "/api/v1/oauth/authorize")
.pathMapping("/oauth/check_token", "/api/v1/oauth/check_token")
.pathMapping("/oauth/confirm_access", "/api/v1/oauth/confirm_access")
.pathMapping("/oauth/error", "/api/v1/oauth/error")
.pathMapping("/oauth/token", "/api/v1/oauth/token")
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(enhancerChain)
.reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
/**
* Forwarding controller.
*
* This controller manages forwarding in particular for the static web clients. Since there are multiple
* clients, this controller will map <i>any</i> GET request to the root /* to one of the clients.
*
* If no match was found, the default redirect goes to /web/index.html
*
*/
#Controller
public class ForwardController {
#RequestMapping(value = "/sitemap.xml", method = RequestMethod.GET)
public String redirectSitemapXml(HttpServletRequest request) {
return "forward:/a/web/assets/sitemap.xml";
}
#RequestMapping(value = "/robots.txt", method = RequestMethod.GET)
public String redirectRobotTxt(HttpServletRequest request) {
return "forward:/a/web/assets/robots.txt";
}
#RequestMapping(value = "/*", method = RequestMethod.GET)
public String redirectRoot(HttpServletRequest request) {
return "forward:/a/web/index.html";
}
#RequestMapping(value = "/a/**/{path:[^.]*}", method = RequestMethod.GET)
public String redirectClients(HttpServletRequest request) {
String requestURI = request.getRequestURI();
if (requestURI.startsWith("/a/admin/")) {
return "forward:/a/admin/index.html";
}
if (requestURI.startsWith("/a/swagger/")) {
return "forward:/a/swagger/swagger-ui.html#/";
}
return "forward:/a/web/index.html";
}
}
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requiresChannel()
/* Require HTTPS evereywhere*/
.antMatchers("/**")
.requiresSecure()
.and()
.exceptionHandling()
.and()
/* Permit all requests towards the public api as well as webhook endpoints. */
.authorizeRequests()
.antMatchers(PUBLIC_ENDPOINTS, WEBHOOK_ENDPOINTS)
.permitAll()
/* Required for ForwardController */
.antMatchers(HttpMethod.GET, "/*")
.permitAll()
.antMatchers("/r/api/**")
.authenticated();
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId(resourceIds)
.authenticationEntryPoint(customAuthEntryPoint());
}
#Component
public class AuthFailureHandler implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
System.out.println("Hello");
// FIXME We need to return HTTP 401 (s.t. the client knows what's going on) but for some reason it's not working as intended!
throw authException;
}
}
#Bean
public AuthenticationEntryPoint customAuthEntryPoint(){
return new AuthFailureHandler();
}
}
}
In addition, here is the WebSecurityConfigurerAdapter although I do not think this plays a role here:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${security.signing-key}")
private String signingKey;
private final UserDetailsService userDetailsService;
private final DataSource dataSource;
#Autowired
public WebSecurityConfig(#Qualifier("appUserDetailsService") UserDetailsService userDetailsService, DataSource dataSource) {
this.userDetailsService = userDetailsService;
this.dataSource = dataSource;
}
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
public void configure(WebSecurity web) throws Exception {
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
/**
* Using {#link JwtTokenStore} for JWT access tokens.
* #return
*/
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* Provide {#link DefaultTokenServices} using the {#link JwtTokenStore}.
* #return
*/
#Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
/**
* We provide the AuthenticationManagerBuilder using our {#link UserDetailsService} and the {#link BCryptPasswordEncoder}.
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(daoAuthenticationProvider());
}
/**
* Using {#link BCryptPasswordEncoder} for user-password encryption.
* #return
*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Provide {#link DaoAuthenticationProvider} for password encoding and set the {#link UserDetailsService}.
* #return
*/
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
return daoAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel()
.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure();
}
}
Create a custom class that extends ResponseEntityExceptionHandler, annotate it with #ControllerAdvice and handle OAuth2Exception (base class of InvalidGrantException).
#ExceptionHandler({OAuth2Exception.class})
public ResponseEntity<Object> handleOAuth2Exception(OAuth2Exception exception, WebRequest request) {
LOGGER.debug("OAuth failed on request processing", exception);
return this.handleExceptionInternal(exception, ErrorOutputDto.create(exception.getOAuth2ErrorCode(), exception.getMessage()), new HttpHeaders(), HttpStatus.valueOf(exception.getHttpErrorCode()), request);
}
Use HandlerExceptionResolverComposite to composite all exception resolvers in the system into one exception resolver. This overrides the corresponding bean defined in
WebMvcConfigurationSupport class. Add list of exceptionResolvers, like DefaultErrorAttributes and ExceptionHandlerExceptionResolver (this enables AOP based exceptions handling by involving classes with #ControllerAdvice annotation such as custom class that we created that extends ResponseEntityExceptionHandler.
<bean id="handlerExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
<property name="exceptionResolvers">
<list>
<bean class="org.springframework.boot.web.servlet.error.DefaultErrorAttributes"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
<property name="messageConverters">
<list>
<ref bean="jackson2HttpMessageConverter" />
</list>
</property>
</bean>
</list>
</property>
Have you tried if defining a #ControllerAdvice specifying your InvalidGrantException works?
#ControllerAdvice
#ResponseBody
public class GlobalExceptionHandler {
#ExceptionHandler(InvalidGrantException.class)
public ResponseEntity<CustomErrorMessageTO> handleInvalidGrant(
InvalidGrantException invalidGrantException) {
CustomErrorMessageTO customErrorMessageTO = new CustomErrorMessageTO("Not granted or whatsoever");
return new ResponseEntity<>(customErrorMessageTO, HttpStatus.UNAUTHORIZED);
}
}
class CustomErrorMessageTO {
private String message;
public CustomErrorMessageTO(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
Related
I'm trying the catch and customize Authentication exception thrown in Customized Authentication Filter but once the exception is thrown it always goes to the provider manager class and sends default spring API error response.
WebSecurity Config class
#Configuration
public static class RestAPISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationEntry authenticationEntryPoint;
#Autowired
BasicAuthenticationProvider customAuthProvider;
#Autowired
AuthenticationFailureHandler accessDeniedHandler;
private final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher(API_PATH_IDENTIFIER));
#Value("${username}")
private String userName;
#Value("${password}")
private String password;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthProvider);
auth.inMemoryAuthentication().withUser("abcd").password(passwordEncoder().encode("sample#123"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/api")
.authenticated().and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(SECURITY_EXCLUDED_PATHS);
}
}
AuthenticationProvider class:
#Component
public class BasicAuthenticationProvider implements AuthenticationProvider {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public Authentication authenticate(Authentication auth) throws UserAuthenticationException
{
String username = auth.getName();
String password = auth.getCredentials()
.toString();
AuthenticationException exception = null;
try {
if ("abcd".equals(username) && "Sample#123".equals(password)) {
return new UsernamePasswordAuthenticationToken
(username, password, Collections.emptyList());
} else {
throw new BadCredentialsException("invalid user");
}
}catch(AuthenticationException e)
{
exception = e;
throw exception;
}
}
#Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
}
AuthenticationEntryPOint class:
/**
*
* Authentication entry point to handle security exceptions
*
*/
#Component
public class AuthenticationEntry implements AuthenticationEntryPoint{
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
/**
*This handles security exceptions and redirects it to exception handler
*/
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws UserAuthenticationException {
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
resolver.resolveException(httpServletRequest, httpServletResponse, null, new UserAuthenticationException("Not Authorized"));
}
}
you can do the exception in the filterChain
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.
Hello everyone hope you doing well,
i have problem using open authentication in spring boot, when accessing page rest with postman is not even using param access token it still show the result, this my code please help???
Authorization Server Config class:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter{
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler);
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO Auto-generated method stub
clients.inMemory()
.withClient("admin").secret("123")
.scopes("read","write")
.authorizedGrantTypes("password","refresh_token")
.accessTokenValiditySeconds(5*60)
.refreshTokenValiditySeconds(10*60);
}
}
Resource Server Config
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
#Override
public void configure(HttpSecurity http)throws Exception{
http
.anonymous().disable()
.authorizeRequests().antMatchers("/api/**") /** this
.authenticated()
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Security Config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private SecurityUtility hash;
#Autowired
private ClientDetailsService clientDetailsService;
private static final String[] PUBLIC_MATCHERS = { "/", "/css/**", "/image/**", "/js/**", "/newUser",
"/forgetPassword", "/login", "/logout", "/fonts/**", "/signUp", "/register", "/sendEmail", "/logout", "/tes","/oauth2/**","/api/**",
"/admin/tes","/SpringSecurityOAuth2Example/**",
"/admin/tes2" };
private static final String[] ADMIN_MATCHERS = { "/admin", "/admin/**" };
private static final String[] OAUTH2_PAGE = { "/oauth/**", "/api/**" };
private final String USERS_QUERY = "select username, password, is_enabled from user where username=?";
private final String ROLES_QUERY = "select u.username, u.is_enabled, r.name as authority from user u "
+ "inner join user_role ur on (u.id = ur.user_id) " + "inner join role r on (ur.role_id = r.roleid) "
+ "where username=?";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(PUBLIC_MATCHERS).permitAll().anyRequest().authenticated().and().formLogin()
.loginPage("/login").loginProcessingUrl("/app-login").usernameParameter("app_username")
.passwordParameter("app_password").defaultSuccessUrl("/myAccount").permitAll()
.and().logout().logoutSuccessUrl("/login")
.permitAll();
http.authorizeRequests().antMatchers(ADMIN_MATCHERS).hasRole("ADMIN");
// http.csrf().disable();
http.csrf().ignoringAntMatchers(OAUTH2_PAGE);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// temporary
// auth.inMemoryAuthentication().withUser("admin").password("admin").roles("test");
auth.jdbcAuthentication().usersByUsernameQuery(USERS_QUERY).authoritiesByUsernameQuery(ROLES_QUERY)
.dataSource(dataSource).passwordEncoder(hash.passwordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#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;
}
}
Auth Controller
#RestController
#EnableResourceServer
public class AuthController {
#GetMapping("/api/demo1")
public String apiTes() {
System.out.println("sysout mas");
return "return result";
}
}
solved guys, it because i was using springboot 1.5.10 so i have to add
security.oauth2.resource.filter-order=3
to spring application.properties
I am writing Spring Boot with OAuth2 application. My classes are:
AuthorizationServerConfig.class
<pre>
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
private static String REALM="CRM_REALM";
private static final int TEN_DAYS = 60 * 60 * 24 * 10;
private static final int ONE_DAY = 60 * 60 * 24;
private static final int THIRTY_DAYS = 60 * 60 * 24 * 30;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CrmUserDetailsService crmUserDetailsService;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws
Exception {
// clients.jdbc(dataSource);
clients.inMemory()
.withClient("crmClient1")
.secret("crmSuperSecret")
.authorizedGrantTypes("password", "refresh_token")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
//.accessTokenValiditySeconds(ONE_DAY)
.accessTokenValiditySeconds(300)
.refreshTokenValiditySeconds(THIRTY_DAYS);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.userDetailsService(crmUserDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.realm(REALM);
}
}
</pre>
ResourceServerConfig.class
<pre>
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
//-- define URL patterns to enable OAuth2 security
http.
anonymous().disable()
.requestMatchers().antMatchers("/api/**")
.and().authorizeRequests()
.antMatchers("/api/**").access("hasRole('ADMIN') or hasRole('USER')")
.and().exceptionHandling().accessDeniedHandler(new
OAuth2AccessDeniedHandler());
}
}
</pre>
SecurityConfig.class
<pre>
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
private CrmUserDetailsService crmUserDetailsService;
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/about").permitAll()
.antMatchers("/signup").permitAll()
.antMatchers("/oauth/token").permitAll()
//.antMatchers("/api/**").authenticated()
.anyRequest().authenticated()
.and()
.httpBasic()
.realmName("CRM_REALM");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(crmUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#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;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
</pre>
SecurityWebApplicationInitializer.class
<pre>
public class SecurityWebApplicationInitializer extends
AbstractSecurityWebApplicationInitializer {
}
</pre>
MethodSecurityConfig.class
<pre>
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration
{
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
</pre>
SignupService.class
<pre>
#Service
#Transactional
public class SignupService {
#Autowired
private UserRepository userRepository;
#Autowired
PasswordEncoder passwordEncoder;
public User addUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
/**
*
* set up a default customer with two roles USER and ADMIN
*
*/
#PostConstruct
private void setupDefaultUser() {
if (userRepository.count() == 0) {
userRepository.save(new User("crmadmin",
passwordEncoder.encode("adminpass"),
Arrays.asList(new UserRole("USER"), new
UserRole("ADMIN"))));
}
}
}
</pre>
After I send request localhost:8080/oauth/token and get access_token and refresh_token later when I try send api request with Authorization: Bearer (access_token) I got error:
<pre>
{
"timestamp": 1534343851414,
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/api/customers"
}
</pre>
Could you help me?
I want to login as a user but for some reason it seems that the endpoint oauth/token is protected:
Request URL:http://192.168.0.14:8080/oauth/token
Request Method:POST
Status Code:401
Remote Address:192.168.0.14:8080
Referrer Policy:no-referrer-when-downgrade
Access-Control-Allow-Headers:x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN
Access-Control-Allow-Methods:PATCH,POST,GET,OPTIONS,DELETE
Access-Control-Allow-Origin:*
Access-Control-Max-Age:3600
Cache-Control:no-store
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Content-Type:application/json;charset=UTF-8
Date:Tue, 06 Mar 2018 18:59:25 GMT
Expires:0
Pragma:no-cache
Pragma:no-cache
Transfer-Encoding:chunked
WWW-Authenticate:Bearer realm="testjwtresourceid", error="unauthorized", error_description="Full authentication is required to access this resource"
WWW-Authenticate:Basic realm="oauth2/client"
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-XSS-Protection:1; mode=block
Interestingly enough... I get this message:
2018-03-06 19:59:25.766 WARN 31030 --- [nio-8080-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
It seems that for some reason the whole thing runs through the BasicAuthenticationFilter. It's clear that this does not look like BCrypt .. though I am setting BCrypt a s my password encoder:
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
auth.authenticationProvider(daoAuthenticationProvider());
}
/**
* Using {#link BCryptPasswordEncoder} for user-password encryption.
* #return
*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
It looks like WebSecurity tries to encode the client secret with BCrypt O_o
security.jwt.client-id=CLIENT_ID
security.jwt.client-secret=CLIENT_SECRET
The entire web config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${security.signing-key}")
private String signingKey;
#Value("${security.encoding-strength}")
private Integer encodingStrength;
#Value("${security.security-realm}")
private String securityRealm;
#Autowired
private UserDetailsService userDetailsService;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* Nothing to configure yet.
* #param http
* #throws Exception
*/
#Override
protected void configure(HttpSecurity http) throws Exception { }
/**
* Define routes for {#link WebSecurity}.
*
* #param web
* #throws Exception
*/
#Override
public void configure(WebSecurity web) throws Exception {
final String[] SWAGGER_UI = {
"/swagger-resources/**",
"/swagger-ui.html",
"/v2/api-docs",
"/webjars/**"
};
web.ignoring().antMatchers("/pub/**", "/users")
.antMatchers(SWAGGER_UI);
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
/**
* Using {#link JwtTokenStore} for JWT access tokens.
* #return
*/
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
/**
* Provide {#link DefaultTokenServices} using the {#link JwtTokenStore}.
* #return
*/
#Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
/**
* We provide the AuthenticationManagerBuilder using our {#link UserDetailsService} and the {#link BCryptPasswordEncoder}.
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()).and()
.authenticationProvider(daoAuthenticationProvider());
}
/**
* Using {#link BCryptPasswordEncoder} for user-password encryption.
* #return
*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Provide {#link DaoAuthenticationProvider} for password encoding and set the {#link UserDetailsService}.
* #return
*/
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
return daoAuthenticationProvider;
}
}
The OAuth config
#Configuration
#EnableAuthorizationServer
#EnableResourceServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests().anyRequest().access("#oauth2.hasScope('write')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceIds);
}
}
#Value("${security.jwt.client-id}")
private String clientId;
#Value("${security.jwt.client-secret}")
private String clientSecret;
#Value("${security.jwt.grant-type}")
private String grantType;
#Value("${security.jwt.scope-read}")
private String scopeRead;
#Value("${security.jwt.scope-write}")
private String scopeWrite;
#Value("${security.jwt.resource-ids}")
private String resourceIds;
#Autowired
private TokenStore tokenStore;
#Autowired
private JwtAccessTokenConverter accessTokenConverter;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(clientId)
.secret(clientSecret)
.authorizedGrantTypes(grantType)
.scopes(scopeRead, scopeWrite)
.resourceIds(resourceIds);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
endpoints.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(enhancerChain)
.authenticationManager(authenticationManager);
}
}
Because you have the following Bean created, all security configurations will use this PasswordEncoder. In your case, WebSecurity and AuthorizationServerSecurity.
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
I am pretty sure your security.jwt.client-secret is a plain text.
So, what is happening is that when you make the request, your AuthorizationSecurityConfig tried to read security.jwt.client-secret as a BCrypt encoded string, which eventually it is not.
Hence, the whole Authentication process fails and the warning message is logged.
To resolve this, you have two options:
First:
Configure the AuthorizationSecurity to use NoOpPasswordEncoder. Here is how you can override this. Mind you NoOpPasswordEncoder is already deprecated.
#Override
public void configure(final AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
Second (Preferred):
Generate the BCrypt value of your security.jwt.client-secret and set it in your application.properties/yaml.
Thanks to David who cleared the doubt on a similar answer here.
Guess what, I was facing the same issue when I was looking at this question for solution. :-)