Customize response parameters of oauth response - spring-boot

I am currently working on a project that is using JDBCTokenStore. By default it return
{
"access_token": "<somedata>",
"token_type": "bearer",
"expires_in": <time>,
"scope": "read write"
}
Is there a way to customize this response?
I tried using TokenEnhancer, but that saves information in DB which is not what I want.
I just want to customize the response and send it back to end user (add an additional parameter like state of the user which changes dynamically based on some criteria).
{
"access_token": "<somedata>",
"token_type": "bearer",
"expires_in": <time>,
"scope": "read write"
"state": <true or false>
}
Any help here would be highly appreciated.

You can customize the token response using a custom TokenEnhancer implementation.
public class CustomTokenEnhancer implements TokenEnhancer {
private TokenEnhancer delegate;
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
OAuth2AccessToken result = delegate.enhance(accessToken, authentication);
result.getAdditionalInformation().put("custom_response_claim", "value");
return result;
}
}
Adding parameters to OAuth2AccessToken.additionalInformation map would be processed as additional parameters in token response.

Related

Spring Authorization server customizing the access token response to add additional params

I am implementing the spring authorization server and I want to add few custom properties to the token response json. Below is how I want the response to be.
{
"access_token": *jwt*,
"scope": "articles.read openid",
"token_type": "Bearer",
"expires_in": 299,
***"customvalue1":99***
}
I have seen multiple posts in stack overflow where similar topic is discussed, but in those scenarios the additional data is added either to the claim or header of jwt. My requirement is to add it outside of the jwt.
I tried to implement OAuth2TokenCustomizer, but this allows only the claims or headers of the jwt to be modified. Can anyone pls help?
To anyone coming here looking for answer:
I ended up overriding OAuth2TokenEndpointFilter. It has a authentication successhandler which can be injected to perform any additional token response manipulation.
#Bean
public Customizer<OAuth2TokenEndpointConfigurer> customizeTokenEndpoint() {
return tokenEndpoint -> tokenEndpoint
.accessTokenResponseHandler(success());
}
#Bean(name = "token")
public AuthenticationSuccessHandler success() {
return new TokenResponseSuccessHandler();
}
Then inside success handler,
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {
final OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
******
**
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
if(additionalParameters.size()==0)
additionalParameters=new HashMap<>();
additionalParameters.put("hi","hi");
Finally use, OAuth2AccessTokenResponse.Builder to build a new response.
In case you are using the new authorization server then creating this bean will help you achieve your goal. The good thing, once the bean is detected it will automatically be applied.
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return context -> {
Authentication principal = context.getPrincipal();
//context.getTokenType().getValue().equals("access_token")
if (Objects.equals(context.getTokenType().getValue(), "access_token") && principal instanceof UsernamePasswordAuthenticationToken) {
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
User user = (User) principal.getPrincipal();
context.getClaims().claim("authorities", authorities)
.claim("user", user);
}
};
}
Duplicate of How to create custom claims in JWT using spring-authorization-server
This class and the method maybe help you.You can find the class init place

How can I refresh tokens in Spring security

This line:
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
Throws an error like this when my jwt token expires:
JWT expired at 2020-05-13T07:50:39Z. Current time:
2020-05-16T21:29:41Z.
More specifically, it is this function that throws the "ExpiredJwtException" exception :
How do I go about handling these exceptions? Should I catch them and send back to the client an error message and force them to re-login?
How can I implement a refresh tokens feature? I'm using Spring and mysql in the backend and vuejs in the front end.
I generate the initial token like this:
#Override
public JSONObject login(AuthenticationRequest authreq) {
JSONObject json = new JSONObject();
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authreq.getUsername(), authreq.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
.collect(Collectors.toList());
if (userDetails != null) {
final String jwt = jwtTokenUtil.generateToken(userDetails);
JwtResponse jwtres = new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(),
userDetails.getEmail(), roles, jwtTokenUtil.extractExpiration(jwt).toString());
return json.put("jwtresponse", jwtres);
}
} catch (BadCredentialsException ex) {
json.put("status", "badcredentials");
} catch (LockedException ex) {
json.put("status", "LockedException");
} catch (DisabledException ex) {
json.put("status", "DisabledException");
}
return json;
}
And then in the JwtUtil class:
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRESIN))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
For more info, here is my doFilterInternal function that filters every request:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {
try {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
boolean correct = jwtUtil.validateToken(jwt, userDetails);
if (correct) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException ex) {
resolver.resolveException(request, response, null, ex);
}
}
There are 2 main approaches to deal with such situations:
Manage access and refresh tokens
In this case, the flow is the following one:
User logins into the application (including username and password)
Your backend application returns any required credentials information and:
2.1 Access JWT token with an expired time usually "low" (15, 30 minutes, etc).
2.2 Refresh JWT token with an expired time greater than access one.
From now, your frontend application will use access token in the Authorization header for every request.
When backend returns 401, the frontend application will try to use refresh token (using an specific endpoint) to get new credentials, without forcing the user to login again.
Refresh token flow
(This is only an example, usually only the refresh token is sent)
If there is no problem, then the user will be able to continue using the application. If backend returns a new 401 => frontend should redirect to login page.
Manage only one Jwt token
In this case, the flow is similar to the previous one and you can create your own endpoint to deal with such situations: /auth/token/extend (for example), including the expired Jwt as parameter of the request.
Now it's up to you manage:
How much time an expired Jwt token will be "valid" to extend it?
The new endpoint will have a similar behaviour of refresh one in the previous section, I mean, will return a new Jwt token or 401 so, from the point of view of frontend the flow will be the same.
One important thing, independently of the approach you want to follow, the "new endpoint" should be excluded from the required Spring authenticated endpoints, because you will manage the security by yourself:
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
..
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
..
.authorizeRequests()
// List of services do not require authentication
.antMatchers(Rest Operator, "MyEndpointToRefreshOrExtendToken").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
..
}
}
You can call the API for getting the refresh token as below
POST https://yourdomain.com/oauth/token
Header
"Authorization": "Basic [base64encode(clientId:clientSecret)]"
Parameters
"grant_type": "refresh_token"
"refresh_token": "[yourRefreshToken]"
Please be noticed that, the
base64encode is the method to encrypt the client authorization. You can use online at https://www.base64encode.org/
the refresh_token is the String value of the grant_type
yourRefreshToken is the refresh token received with JWT access token
The result can be seen as
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Good luck.

Change the Bad credentials error response spring security oauth2

I have a AuthorizationServer which uses password grant_type using spring security. I am using this for mobile application, when a user enter username password to log in, the app calls the token endpoint and generate a token if he/she is an authenticated user. This is all handled by password grant_type itself. For a unsuccessful log in it returns below general error with 400 HTTP status code.
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
But for my scenario I need customize this error message. Is their a way to change this error message ?
Note that i tried the suggested duplicate question -
Customize authentication failure response in Spring Security using AuthenticationFailureHandler
but it uses the formLogin and it's not working with my implementation.
Thank you,
Rajith
I couldn't find an answer to this problem for many days. Finally, I got help from one of my colleagues. He asked me to follow this tutorial and it worked for me. Now I could transform the default spring framework response to my response template as follows.
{
"status": 400,
"message": "Invalid username or password",
"timestamp": "2020-06-19T10:58:29.973+00:00",
"payload": null
}
But still, we don't know, why authenticationFailure handler is not working. Hope this helps.
If you want to change only the message text in the response, than it will be enough to add the messages.properties file to the classpath of your application with the following content:
AbstractUserDetailsAuthenticationProvider.badCredentials=Invalid username or password
This will lead to the response below:
{
"error": "invalid_grant",
"error_description": "Invalid username or password"
}
Sabin answer is works, but i need to throw the exception using BadCredentialsException,
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final static Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
#Autowired
private UserService userService;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException{
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
try {
/* CHECKING USER CREDENTIAL */
/* check account */
User userDetail = userService.findByUsername(username);
if (userDetail == null){
throw new Exception("User not found!");
}
/* check password */
String origPass = Utilities.getEncrypted(new String(Base64.decodeBase64(password)), username);
if(!userDetail.getPassword().equals(origPass)){
throw new Exception("Wrong username or password!");
}
/* check is active */
if(!userDetail.getIsActive()){
throw new Exception("User is not active!");
}
/* check allowance in web type */
if(Access.isWeb()){
if(!userDetail.getIsWeb())
throw new Exception("Web access prohibited!");
}
/* check allowance in mobile type */
if(Access.isMobile()){
if(!userDetail.getIsMobile())
throw new Exception("Mobile access prohibited!");
}
/* do some logs */
userService.login(userDetail);
return new UsernamePasswordAuthenticationToken(userDetail, "{noop}".concat(origPass), userDetail.getAuthorities());
} catch (Exception e) {
LOGGER.error("[OAUTH] Error : " + e.getLocalizedMessage());
throw new BadCredentialsException(e.getLocalizedMessage(), e);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

Spring boot Authorization server redirection issue to client after successful authentication via Facebook

I am trying to setup a spring boot Authorizaiton server which will have the internal user login and OAuth2 with facebook. I am facing the below issues -
If I make my Authorization server SessionCreationPolicy.STATELESS then after successful authentication from facebook control get stuck in Authorization server itself (Its not returning to the my client application while if SessionCreationPolicy.IF_REQUIRED then control returns to my client app).
When I am using SessionCreationPolicy.IF_REQUIRED then control returns and I can do a authorization_code flow but the jwt token generated by spring-security-jwt gives me only user_name information in token which is facebook user's id (not even name).
My local user authentication code works fine with code flow and I can customize my token with custom token enhancer and add other properties also but when I try to customize facebook principal object to my custom user I get error that string can not be casted to custom user object.
Please refer to the repo for details - dev repo
I am using below code for setup/jwt generation -
#EnableOAuth2Client // for Oauth setup
// jwt enhancer which gives me error when principal is converted to custom user
class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
Authentication auth = authentication.getUserAuthentication();
/* additionalInfo.put("email", ((CustomPrincipal)auth.getPrincipal()).getEmail());
additionalInfo.put("roles", ((CustomPrincipal)auth.getPrincipal()).getRoles());
additionalInfo.put("id", ((CustomPrincipal)auth.getPrincipal()).getId());*/
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
//SSO filter i am using -
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId());
tokenServices.setRestTemplate(template);
filter.setTokenServices(tokenServices);
// filter.setAuthenticationSuccessHandler(authenticationHandler);
return filter;
}
Any help is appreciated.
Thanks!
I was able to get an explanation for 2nd and 3rd point-
Since after the authentication is successful from Facebook; Spring boot authorization server stores authentication object as below format -
{
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "xyzxyzxyzxyzxyz",
"tokenValue": "xyzxyzxyzxyzxyz",
"tokenType": "bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"id": "xyzxyzxyzxyzxyz",
"name": "xyzxyzxyzxyzxyz",
"email": "xyzxyzxyzxyzxyz"
},
"authenticated": true,
"principal": "xyzxyzxyzxyzxyz",
"credentials": "N/A",
"name": "xyzxyzxyzxyzxyz"
},
"principal": "xyzxyzxyzxyzxyz",
"oauth2Request": {
"clientId": "xyzxyzxyzxyzxyz",
"scope": [],
"requestParameters": {},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"grantType": null,
"refreshTokenRequest": null
},
so when I was casting my principal to custom principal I was getting the error since in above model principal is just a string.
Note - I still have no idea how i can customize the above authentication object to my customuser object.

How to use ResourceOwnerPasswordCredentialsGrant with swagger ui

I am using swagger, swagger ui with spring rest api to get a platform for testing/documenting the API, so I need to get oAuth2 authorisation working in swagger ui, I am using password grant with the authorisation server, so I had to use ResourceOwnerPasswordCredentialsGrant from the package springfox.documentation.servicewhich has a single parameter to its constructor, namely, the token url, I am setting that to the token endpoint in my authorisation server, but unfortunately, it does not persist token url and shows that as null in the authorisation window as follows:
I could not find any example to use this particular type of grant with swagger ui, any help is much appreciated.
This is my configuration
public Docket oauth() {
return new Docket(DocumentationType.SWAGGER_2).groupName("oauth")
.securitySchemes(Arrays.asList(userOAuthScheme())).securityContexts(Arrays.asList(securityContext()))
.select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
.paths(not(ant("/admin/**")))
.paths(not(ant("/admin.json")))
.paths(not(ant("/error/**")))
.paths(not(ant("/exception/**")))
.paths(not(ant("/ping/**"))).build();
}
private OAuth userOAuthScheme() {
List<AuthorizationScope> authorizationScopeList = new ArrayList<AuthorizationScope>();
GrantType grantType = new ResourceOwnerPasswordCredentialsGrant("http://localhost:8080/authServer/oauth/token");
return new OAuth("oauth2", authorizationScopeList, Arrays.asList(grantType));
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.any()).build();
}
#Bean
public SecurityConfiguration securityInfo() {
return new SecurityConfiguration("myClientId", "myClientSecret", "", "", "", ApiKeyVehicle.HEADER, "",
" ");
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[0];
return Arrays.asList(new SecurityReference("oauth2", authorizationScopes));
}
On the Swagger screen take care in the "Setup client authentication" section
Type: Basic auth/ Request Body
It depends on your implementation, in my case works Basic auth.
I dont use scopes but you can add it on
AuthorizationScope[] authorizationScopes
List<AuthorizationScope> authorizationScopeList

Resources