Spring Boot with Spring Security - Authorization with Method Level Security with #PreAuthorize, #RolledAllowed or #Secured Not Working - spring-boot

I have a Spring Boot application that users Spring Security. My Authentication and Authorization filters are working as expected.
In my Authentication filter, I generate JWT token with list of user authorities set as claim, and send the generated JWT together with claims back to client as part of "Auth" header. That is all working great.
In Authorization filter, I also got it all working fine, my doFilterInternals() override does proper chaining and it also calls my getAuthenticationToken() method:
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
String token = request.getHeader("Auth");
if (token != null) {
token = token.replace("Bearer", "");
String username = Jwts.parser()
.setSigningKey(SecurityConstants.getTokenSecret())
.parseClaimsJws(token)
.getBody()
.getSubject();
String authoritiesString = Jwts.parser()
.setSigningKey(SecurityConstants.getTokenSecret())
.parseClaimsJws(token)
.getBody()
.get("user-authorities").toString(); //authority1, authority2, ...
if (username != null) {
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesString);
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
}
return null;
}
Above, I extract authorities (these are user groups coming from Active Directory) from claim I named"user-authorities" and I generate new UsernamePasswordAuthenticationToken with the authorities and return it.
This is all working great and I have been using for a while now.
Now, I am trying to use these authorities to add method level security to my controllers.
In order to do so, I have a #Configuration class which I annotated with #EnableGlobalMethodSecurity:
#Configuration
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class AppConfig {
...
}
Then on my controller I am using #Secured("authority1") to secure my controller:
#PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
#Secured("authority1")
public CarResponse saveCar(#Valid #RequestBody CarRequest carRequest, #RequestHeader HttpHeaders httpHeaders) {
System.out.println("Received :" + carRequest.toString());
return null;
}
I know JWT token contains claims with "authority1,authority2,authority3" comma-delimited string of authorities. So, my expectation would be that the controller below will execute for a user who authenticates and has these 3 authorities.
However, what I get back is 500 error. If I comment out the #Secured annotation, my controller will execute just fine but then it is not secured. I have also tried using #PreAuthorized("hasRole('authority1')") and also #RolesAllowed("authority1") but none are workng.
I dont know what I am missing.

Related

How to retrieve attributes and username sent by the CAS server with Spring Security

I have a spring boot application, which is MVC in nature. All page of this application are being authenticated by CAS SSO.
I have used "spring-security-cas" as described at https://www.baeldung.com/spring-security-cas-sso
Everything working fine as expected. However, I have one problem - that is, I cannot retrieve attributes
and username sent by the CAS server in the following #Bean. What need I do to retrieve all the attributes
and and username sent by the CAS server?
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User("casuser", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
return provider;
}
First you will need to configure the attributeRepository source and the attributes to be retrieved, in attributeRepository section in CAS server, like:
cas.authn.attributeRepository.jdbc[0].singleRow=false
cas.authn.attributeRepository.jdbc[0].sql=SELECT * FROM USERATTRS WHERE {0}
cas.authn.attributeRepository.jdbc[0].username=username
cas.authn.attributeRepository.jdbc[0].role=role
cas.authn.attributeRepository.jdbc[0].email=email
cas.authn.attributeRepository.jdbc[0].url=jdbc:hsqldb:hsql://localhost:9001/xdb
cas.authn.attributeRepository.jdbc[0].columnMappings.attrname=attrvalue
cas.authn.attributeRepository.defaultAttributesToRelease=username,email,role
Check this example from CAS blog.
Then you need to implement an AuthenticationUserDetailsService at the service to read attributes returned from CAS authentication, something like:
#Component
public class CasUserDetailService implements AuthenticationUserDetailsService {
#Override
public UserDetails loadUserDetails(Authentication authentication) throws UsernameNotFoundException {
CasAssertionAuthenticationToken casAssertionAuthenticationToken = (CasAssertionAuthenticationToken) authentication;
AttributePrincipal principal = casAssertionAuthenticationToken.getAssertion().getPrincipal();
Map attributes = principal.getAttributes();
String uname = (String) attributes.get("username");
String email = (String) attributes.get("email");
String role = (String) attributes.get("role");
String username = authentication.getName();
Collection<SimpleGrantedAuthority> collection = new ArrayList<SimpleGrantedAuthority>();
collection.add(new SimpleGrantedAuthority(role));
return new User(username, "", collection);
}
}
Then, adjust your authenticationProvider with provider.setAuthenticationUserDetailsService(casUserDetailService);

Are there any endpoint for check token in ADFS?

I am using Spring Oauth2 and ADFS for security purpose. However I can not find the endpoint for checking token from response of ADFS.
I also have Spring Authorization Provider which is written in Java. And my application called it by using these properties:
security.oauth2.client.clientId=myclient
security.oauth2.client.client-secret= mysecret
security.oauth2.client.userAuthorizationUri= http://127.0.0.1:9999/oauth/authorize?resource=https://localhost:8443/login
security.oauth2.client.accessTokenUri= http://127.0.0.1:9999/oauth/token
security.oauth2.resource.user-info-uri= http://127.0.0.1:9999/login
security.oauth2.resource.token-info-uri= http://127.0.0.1:9999/oauth/check_token
security.oauth2.client.tokenName=code
security.oauth2.client.authenticationScheme=query
security.oauth2.client.clientAuthenticationScheme=form
security.oauth2.client.grant-type=authorization_code
And I have changed the values of the properties to connect with ADFS
security.oauth2.client.clientId=myclient
security.oauth2.client.client-secret= myclient
security.oauth2.client.userAuthorizationUri= https://adfs.local/adfs/oauth2/authorize?resource=https://localhost:8443/login
security.oauth2.client.accessTokenUri= https://adfs.local/adfs/oauth2/token
security.oauth2.resource.user-info-uri= https://adfs.local/adfs/oauth2/userinfo
security.oauth2.resource.token-info-uri= https://adfs.local/adfs/oauth2/check_token
security.oauth2.client.tokenName=code
security.oauth2.client.authenticationScheme=query
security.oauth2.client.clientAuthenticationScheme=form
security.oauth2.client.grant-type=authorization_code
However, I found that https://adfs.local/adfs/oauth2/check_token is invalid in ADFS.
How can I get the check_token in ADFS? check_token is Token Introspection Endpoint, however, this endpoint doesn't return node 'active' according to OAuth 2 Extension which is mandatory. See this link
This is what Spring Authorization Provider do when return check_token endpoint
#RequestMapping(value = "/oauth/check_token", method = RequestMethod.POST)
#ResponseBody
public Map<String, ?> checkToken(#RequestParam("token") String value) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}
if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, Object> response = (Map<String, Object>)accessTokenConverter.convertAccessToken(token, authentication);
// gh-1070
response.put("active", true); // Always true if token exists and not expired
return response;
}
ADFS has no such endpoint and I don't believe it's part of the spec?
You could use:
https://[Your ADFS hostname]/adfs/.well-known/openid-configuration
to get the keys to check the JWT yourself which is the usual practice.
There are many resources on how to check the JWT e.g. this.

How can I validate OAuth 2.0 token user details in #PreAuthorize annotation in Spring Boot REST service

I need to make a check in #PreAuthorize annotation. Something like:
#PreAuthorize("hasRole('ROLE_VIEWER') or hasRole('ROLE_EDITOR')")
That is OK but I also need to validate some user details stored in the OAuth 2.0 token with those in the request path so I would need to do something like (oauthToken.userDetails is just an example:
#PreAuthorize("#pathProfileId.equals(oauthToken.userDetails.profileId)")
(profileId is not userId or userName, it is a user details that we add in the OAuth token when we create it)
What is the simplest way to make OAuth token properties visible in the preauthorized annotation security expression language?
You have two options:
1-
Setting UserDetailsService instance into DefaultUserAuthenticationConverter
and set converter to JwtAccessTokenConverter so when spring calls extractAuthentication method from DefaultUserAuthenticationConverter it found (userDetailsService != null) so it get the whole UserDetails object by calling implementation of loadUserByUsername when calling this line:
userDetailsService.loadUserByUsername((String) map.get(USERNAME))
implemented in next method inside spring class org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter.java but just adding it to clarify how spring get principal object from map (first getting it by username, and if userDetailsService not null so it get the whole object):
//Note: This method implemented by spring but just putting it to show where spring exctract principal object and how extracting it
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
So what you need to implement in your microservice is:
#Bean//this method just used with token store bean example: new JwtTokenStore(tokenEnhancer());
public JwtAccessTokenConverter tokenEnhancer() {
/**
* CustomTokenConverter is a class extends JwtAccessTokenConverter
* which override "enhance" to add extra information to OAuth2AccessToken after
* authenticate the user and get it by loadUserByUsername implementation
* like profileId in your case
**/
JwtAccessTokenConverter converter = new CustomTokenConverter();
DefaultAccessTokenConverter datc = new DefaultAccessTokenConverter();
datc.setUserTokenConverter(userAuthenticationConverter());
converter.setAccessTokenConverter(datc);
//Other method code implementation....
}
#Autowired
private UserDetailsService userDetailsService;
#Bean
public UserAuthenticationConverter userAuthenticationConverter() {
DefaultUserAuthenticationConverter duac = new DefaultUserAuthenticationConverter();
duac.setUserDetailsService(userDetailsService);
return duac;
}
Note: this first way will hit database in every request so it load user by username and get UserDetails object so it assign it to principal object inside authentication.
2-
If for any reason you see it's better to not hit database in each request and no problem about executing data needed like profileId from token passed in request.
Assuming you know that old authorities assigned to user when generating oauth2 token will always be in token till it goes invalid even after you change it in database for user who passes the token in request so user could call a method not allowed to him/her anymore after extracting token and it was allowed before extracting the token.
So this means if user authorities changed after generating the token, new authorities will not be checked by #PreAuthorize as it's not removed or added to token and you have to wait till old token goes invalid or expired so user forced to execute the service again to get new oauth token.
Anyway, in this second option you only need to override extractAuthentication method inside CustomTokenConverter class extends JwtAccessTokenConverter and forget about setting access token converter converter.setAccessTokenConverter from tokenEnhancer() method in first option, and here are the whole CustomTokenConverter you can use it for reading data from token and return principal object not just string username:
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
public class CustomTokenConverter extends JwtAccessTokenConverter {
// This is the method you need to override to read data direct from token passed in request
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Object userIdObj = map.get(AuthenticationUtils.USER_ID);
UUID userId = userIdObj != null ? UUID.fromString(userIdObj.toString()) : null;
Object profileIdObj = map.get(AuthenticationUtils.PROFILE_ID);
UUID profileId = profileIdObj != null ? UUID.fromString(profileIdObj.toString()) : null;
Object firstNameObj = map.get(AuthenticationUtils.FIRST_NAME);
String firstName = firstNameObj != null ? String.valueOf(firstNameObj) : null;
Object lastNameObj = map.get(AuthenticationUtils.LAST_NAME);
String lastName = lastNameObj != null ? String.valueOf(lastNameObj) : null;
JwtUser principal = new JwtUser(userId, profileId, authentication.getUserAuthentication().getName(), "N/A", authentication.getUserAuthentication().getAuthorities(), firstName, lastName);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(),
new UsernamePasswordAuthenticationToken(principal, "N/A", authentication.getUserAuthentication().getAuthorities()));
return authentication;
}
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
JwtUser user = (JwtUser) authentication.getPrincipal();
Map<String, Object> info = new LinkedHashMap<>(accessToken.getAdditionalInformation());
if (user.getId() != null)
info.put(AuthenticationUtils.USER_ID, user.getId());
if (user.getProfileId() != null)
info.put(AuthenticationUtils.PROFILE_ID, user.getProfileId());
if (isNotNullNotEmpty(user.getFirstName()))
info.put(AuthenticationUtils.FIRST_NAME, user.getFirstName());
if (isNotNullNotEmpty(user.getLastName()))
info.put(AuthenticationUtils.LAST_NAME, user.getLastName());
DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
return super.enhance(customAccessToken, authentication);
}
private boolean isNotNullNotEmpty(String str) {
return Optional.ofNullable(str).map(String::trim).map(string -> !str.isEmpty()).orElse(false);
}
}
Finally: Guess how i know you are asking about JWT used with OAuth2?
Because i am a part of your company :P and you know that :P

Spring OAuth/JWT get extra information from access token

I made a simple application that use spring security with oauth/jwt provider.
I added extra information in jwt token by custom JwtAccessTokenConverter and it works well.
My issue is how gets these extra informations in my Rest Controller.
This is my test:
#RequestMapping(value = "/test", produces = { "application/json" },method = RequestMethod.GET)
public String testMethod(OAuth2Authentication authentication,
OAuth2AccessToken token,
Principal user){
.....
Object a=token.getAdditionalInformation();
Object b=token.getValue();
...
}
The results are:
OAuth2Authentication: well inject but don't contain additional informations or accesstoken object (it contains only the original jwt token string).
User is a reference to OAuth2Authentication
OAuth2AccessToken: is aop proxy without any information infact object A and B are null.
Some extra info:
I checked,by debug, that ResourceService use my JwtAccessTokenConverter and extract the list of additional information from the access token string in input.
I found a possible solution.
I set in my JwtAccessTokenConverter a DefaultAccessTokenConverter where i set my custom UserTokenConverter.
So..
The JwtAccessTokenConverter manage only the jwt aspect of access token (token verification and extraction), the new DefaultAccessTokenConverter manages the oauth aspect of access token convertion including the use of my custom UserTokenConverter to create the Pricipal with custom informations extracted from jwt token.
public class myUserConverter extends DefaultUserAuthenticationConverter {
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
// Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
UserDto utente = new UserDto();
utente.setUsername(map.get(USERNAME).toString());
utente.setUfficio(map.get("ufficio").toString());
utente.setExtraInfo(map.get("Informazione1").toString());
utente.setNome(map.get("nome").toString());
utente.setCognome(map.get("cognome").toString());
utente.setRuolo(map.get("ruolo").toString());
return new UsernamePasswordAuthenticationToken(utente, "N/A", authorities);
}
return null;
}

Not getting Client Authority/Role while using RemoteTokenService

I am using Spring-Security-OAuth2 for implementing my own oauth server and resource server. I am using RemoteTokenService as my ResourceServerTokenService on my ResourceServer which will authenticate any accessToken using the CheckTokenEndpoint (/oauth/check_token) on OAuth Server.
I have added a antMatcher for an api url e.g. /data/list which will need client application Role / Authority: "ROLE_ADMIN" like this .antMatcher('/data/list').access("#oauth2.clientHasRole('ROLE_ADMIN')")
but it is not working.
I have done some trial and error on this end point and what I get is following :::
When oauth grant is client only i.e. client_credential grant.
what we get from /oauth/check_token
{
"scope":["read"],
"exp":1412955393,
"client_id":"sample_test_client_app"
}
we dont get any client authority. so how can spring security will perform above authorization check of "#oauth2.clientHasRole('ROLE_ADMIN')"
When oauth grant is user + client i.e. Authorization_code grant
what we get from /oauth/check_token
{
"aud":["resource_id"],
"exp":1412957540,
"user_name":"developer",
"authorities":["ROLE_USER"],
"client_id":"sample_test_client_app",
"scope":["read"]
}
and for authorization_code grnat we are still not getting client authority/role. so can any one tell me how can we perform clientHasRole authentication on any api url?
For "#oauth2.clientHasRole('ROLE_ADMIN')" to work we have to implemented our AccessTokenConverter and inject it into auth server and resource server.
so create a new class which extends DefaultAccessTokenConverter and override convertAccessToken and extractAuthentication methods.
In convertAccessToken method just add
Map<String, Object> response = (Map<String, Object>) super.convertAccessToken(token, authentication);
OAuth2Request clientToken = authentication.getOAuth2Request();
response.put("clientAuthorities", clientToken.getAuthorities());
and in extractAuthentication method add
Collection<HashMap<String, String>> clientAuthorities = (Collection<HashMap<String, String>>) map.get("client_authority");
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (HashMap<String, String> grantedAuthority : clientAuthorities) {
for (String authority : grantedAuthority.values()) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority));
}
}
Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? (Collection<String>) map.get(AUD) : Collections.<String> emptySet());
OAuth2Request request = new OAuth2Request(parameters, clientId, grantedAuthorities, true, scope, resourceIds, null, null, null);
At auth server :
set this class in AuthorizationServerEndpointsConfigurer
At resource server :
set this class in RemoteTokenServices

Resources