Customize Spring Security ResourceServer response - spring

I have a spring security config that looks something like what I have below:
#Configuration
#EnableResourceServer
#EnableConfigurationProperties(OAuthSettings.class)
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Autowired
private OAuthSettings oAuthSettings;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
final JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();
jwtTokenEnhancer.setVerifierKey(oAuthSettings.getPublicKey());
jwtTokenEnhancer.afterPropertiesSet();
JwtTokenStore tokenStore = new JwtTokenStore(jwtTokenEnhancer);
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**")
.access(String.format("#oauth2.hasScope('%s')", oAuthSettings.getRequiredScope()));
}
}
If I send in a bad token, I get this response:
{
"error": "invalid_token",
"error_description": "Encoded token is a refresh token"
}
I would really like to customize this response.
Example, maybe I'd like to send back an object with a few more (or different) properties. For example a response like:
{
"error": "invalid_token",
"errorDescription": "Encoded token is a refresh token",
"aSuggestion": "some suggestion",
"anotherProperty": "check this out!"
}
I am unable to find a hook in spring security to allow me to override this default exception handling behavior. Any help is appreciated.

You can override the commence method in EntryPoint
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException;

Related

Authorize using acess token

I have secured my application using oauth2 standard flow with amazon cognito. It works fine.
Sometimes i want to link to my webapp through my mobile app, and i dont want the user to sign in again. How do i create an #AnonymousAllowed endpoint that takes in an access token, authenticates using spring security and redirects to the homepage of my application?
I've tried creating an endpoint that returns the jsessionid, then another anonymous endpoint that accepts a jsessionid, sets the cookie and redirects to the homepage, but it didnt work.
I've tried setting the authorization header to the access token. But i feel like that wont work properly because the token will expire after few minutes, and spring wont handle authorization for me, rather i have to do it "manually"
I don't know if this can help in your situation. But you can check if anything you can refer from below. It's a custom filter with custom token validator.
You can use Custom TokenAuthenticationFilter by extending AbstractAuthenticationProcessingFilter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private TokenValidatorService tokenValidatorService;
public CustomTokenAuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token= httpServletRequest.getHeader(CommonConstants.OAUTH_HEADER_KEY_AUTHORIZATION);
//Get the custom header
//Validate token using custom validator based on header value
Optional<OAuthResponseData> oauthResponseData = tokenValidatorService.validateAccessToken(token, provider);
...
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(oauthResponseData.get(), oauthResponseData.get().getOauthToken());
return getAuthenticationManager().authenticate(requestAuthentication);
}
#Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
In TokenValidatorService you can implement OAuth2TokenValidator to validate the token
Spring Security config class:
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {
#Configuration
public static class CustomTokenSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
protected CustomTokenAuthenticationProvider customTokenAuthenticationProvider;
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring()
...
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS).authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
public CustomTokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
final CustomTokenAuthenticationFilter filter = new CustomTokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
// filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customTokenAuthenticationProvider);
}
}
}

How to map 403 error to user friendly error?

I'm new to building rest APi using spring boot.
Here is my controller snippet
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value = "/api/post/posts", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PostDto> createPost(#Valid #RequestBody PostDto postDto) {
System.out.println("postDto : " + postDto.getId());
return new ResponseEntity<>(postService.createPost(postDto), HttpStatus.CREATED);
}
this is my Security config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true) //give method level security
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/api/**").permitAll().anyRequest()
.authenticated().and().httpBasic();
}
#Override
#Bean
protected UserDetailsService userDetailsService() {
// In Memory Users
UserDetails ashish = User.builder().username("oxana").password(getPasswordEncoder().encode("password")).roles("USER").build();
UserDetails admin = User.builder().username("admin").password(getPasswordEncoder().encode("admin")).roles("ADMIN").build();
return new InMemoryUserDetailsManager(ashish, admin);
}
#Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
I'm trying land above exception here
#ExceptionHandler(Exception.class)
public ResponseEntity<Errors> handleGlobalException(Exception exception,
WebRequest webRequest){
Error errorDetails = new Error();
errorDetails.setErrorDesc(exception.getMessage());
errorDetails.setErrorCode(Error.ErrorCodeEnum.BAD_REQUEST);
Errors errors = new Errors();
errors.addErrorsItem(errorDetails);
return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
}
but its not coming and giving a big mess of error, like this
"timestamp": "2022-02-21T11:39:28.797+00:00",
"status": 403,
"error": "Forbidden",
"trace": "org.springframework.security.access.AccessDeniedException: Access is denied\r\n\tat org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73)\r\n\tat org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:238)\r\n\tat org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208)\r\n\tat org.springframework.security.access.intercept.aopalliance.
Can anyone please suggest me, How can I handle or catch this exception to customize error, where user has no access to do something ?
Thanks
Update
Implemented AccessDeniedHandler in below way
#ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Dont have sufficient priviliges to perform this action")
public class AccessDeniedError implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exec)
throws IOException, ServletException {
response.sendRedirect("Dont have sufficient priviliges to perform this action");
}
}
And now able to get message like this
{
"timestamp": "2022-02-21T13:29:08.377+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/post/Dont%20have%20sufficient%20priviliges%20to%20perform%20this%20action"
}
Its somewhat better, but how can I take control of these variables ("error", "message", "status") values from above response, so that I could add mine custom values in it ?
The AccessDeniedException is handled by the ExceptionTranslationFilter which then delegates to the AccessDeniedHandler to write to corresponding response to the client.
If you want to customize this behavior, then you can implement a AccessDeniedHandler and then set your implementation to the HttpSecurity object.
MyAccessDeniedHandler.java
public class MyAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
writeCustomResponse(response);
}
private void writeCustomResponse(HttpServletResponse response) {
if (!response.isCommitted()) {
try {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("{ \"error\": \"User is not authorized.\"}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
EDIT
Since WebSecurityConfigurerAdapter was deprecated in Spring Security 5.7.0-M2, the following shows how to configure it by registering a SecurityFilterChain bean.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler())
return http.build();
}
OLD WAY
SecurityConfiguration.java
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// set the customized AccessDeniedHandler to the HttpSecurity object
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
}
}

Spring boot OAuth 2 security get access token from Refresh token(if expired)

I have implemented Spring boot Oauth 2 security it works fine but when I try to get access token (if expired) from refresh token it gives me an error
{
"error": "unauthorized",
"error_description": "admin"
}
Console Log
Handling error: UsernameNotFoundException, admin
Following is my code
1.WebSecurityConfigure
#Configuration
#EnableWebSecurity
public class EmployeeSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/user/getEmployeesList")
.hasAnyRole("USER").anyRequest().authenticated().and().formLogin()
.permitAll().and().logout().permitAll();
http.csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationMgr) throws Exception {
authenticationMgr.inMemoryAuthentication().withUser("admin").password("admin")
.authorities("ROLE_USER");
;
}
}
2.AuthorizationServerConfigure
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("MagicUser").authorizedGrantTypes("authorization_code", "refresh_token","password")
.authorities("CLIENT").scopes("openid", "read", "write", "trust").resourceIds("oauth2-resource")
.redirectUris("http://10.9.6.31:8090/showEmployees").accessTokenValiditySeconds(5000).secret("secret")
.refreshTokenValiditySeconds(50000);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
;
}
}
Please help me to figure out this issue
Access token request (if expired)
http://10.9.6.31:8091/oauth/token
Body parameter
grant_type=refresh_token
refresh_token=78d2ab82-46a2-4b70-a9e8-e3f9e5ddfec6
You might have to send an authorization header in the request to authenticate the client.
Check OAuth client authentication and Refreshing an access token specifications in OAuth 2.0 specification.

Vue Js and Spring Boot Basic Authentication

I have a basic Spring Boot API with Spring Security enabled. When accessing a secured resource from within Vue (using axios), the browser will request me for username and password with an "Authorization Required" pop-up. After that, the credentials seem to be stored by the browser and I can just keep on making requests.
How should I bypass the authentication process made by the browser and replace it with one made and controlled directly by Vue Js?
First, add security configuration (Assuming you are using Spring Security):
#Configuration
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and().httpBasic().authenticationEntryPoint(apiAwareLoginUrlAuthenticationEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
#Bean
public ApiBasicAuthenticationEntryPoint apiAwareLoginUrlAuthenticationEntryPoint() {
ApiBasicAuthenticationEntryPoint entryPoint = new ApiBasicAuthenticationEntryPoint();
entryPoint.setRealmName("Api Server");
return entryPoint;
}
public static class ApiBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
//response.setContentType("");
PrintWriter writer = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(writer, ApiDataGenerator.buildResult(
ErrorCode.AUTHORIZATION_REQUIRED, "Authorization failed"));
}
}
}
Second, Add authentication in the http request header in following format:
Authorization: Basic qwerasdfzxcv
qwerasdfzxcv is base64 hash that encode by username:password

spring security token request requires authentication

I am trying to implement Authorization Code Grant Flow of OAuth 2.0. But stuck with the issue of Authentication popup on token request.
Here is my code.
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
#Configuration
public class SecurityConfig
extends WebSecurityConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("abc").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and().csrf().disable();
}
}
#Configuration
#EnableAuthorizationServer
public class AuthServerOAuth2Config
extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test")
.secret("test_secret")
.authorizedGrantTypes("authorization_code")
.scopes("write");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.authorizationCodeServices(authorizationCodeServices())
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.approvalStoreDisabled();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
}
To get token I do the following steps:
Using browser go to:
http://localhost:9000/oauth/authorize?response_type=code&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2F&scope=write
First it redirects me to a Login form, where I enter username and passord: admin abc
Then it asks if I allow to provide the permission to my "test" client.
It redirects me to "redirect uri": http://localhost:8080?code=XXX
Then I copy code and use Google Advanced Rest Client to send Token Request:
POST on http://localhost:9000/oauth/token?client_id=test&grant_type=authorization_code&code=XXX
Without any headers. As far as I understand Poster should use Browser cookie.
As result on token request I see a popup asking to fill username and password while expecting to get access token in response.
Please, help me to solve the issue. Should I add some headers to my token request? Or my Authorization Server config is not correct?
I found the reason of the issue by myself just reading others resources of OAuth2 specification.
It it is required to send Authorization on token request with the following value:
Authorization: Basic {base64 encode of clientId:clientSecret}

Resources