How can I get the current user roles from Spring security 3.1 - spring

I have loaded the roles from the database for the current user. And I can access the user role with spring security expression in JSP, and can hide the options and URLs which are not authorized with hasRole. Now I wanted to have it in the servlet and display it in the logs (or store in the user object session). How can we achieve it?

You can try something like this:
Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>) SecurityContextHolder.getContext().getAuthentication().getAuthorities();
You have the collection of roles in the authorities variable.

If you develop on Java 8, it's getting easier.
To get all user roles:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = authentication.getAuthorities().stream()
.map(r -> r.getAuthority()).collect(Collectors.toSet());
To check if the user has a particular role, for example, ROLE_USER:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean hasUserRole = authentication.getAuthorities().stream()
.anyMatch(r -> r.getAuthority().equals("ROLE_USER"));

Try to call getUserPrincipal() from HttpServletRequest.

I've created a custom hasRole function for my project.
public static boolean hasRole (String roleName)
{
return SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(roleName));
}

To complete both answers...
Here is a Spring security implementation of getUserPrincipal, so you can see that the getUserPrincipal actually is SecurityContextHolder
public Principal getUserPrincipal() {
Authentication auth = getAuthentication();
if ((auth == null) || (auth.getPrincipal() == null)) {
return null;
}
return auth;
}
// And the getAuthentication
private Authentication getAuthentication() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!trustResolver.isAnonymous(auth)) {
return auth;
}
return null;
}

This may help someone.
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.ui.Model;
import org.springframework.security.core.userdetails.User;
#GetMapping("/home")
public String getHomePage(Authentication authentication, Model model) {
User u = (User) authentication.getPrincipal();
model.addAttribute("cu", u);
return "sb/homePage";
}
And in template Thymeleaf:
Current user:</br>
<div th:if="${cu}">
Username: [[${cu.username}]]</br>
Password: [[${cu.password}]]</br>
Role: [[${cu.authorities[0]}]]</br>
Enabled: [[${cu.enabled}]]</br>
Full: [[${cu}]]</br>
</div>
<div th:unless="${cu}">
Not logged-in!
</div>

Related

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

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.

Is there a way to use only `/login/oauth2/code/:registration_id` in spring security?

First of all, I don't want to provide a web view for social login on my mobile
The social login method for spring security is to call /oauth2/authorization/:registration_id and then /login/oauth2/code/:registration_id.
However, if you are using SDK on mobile, I don't need /oauth2/authorization/:registration_id, only need /login/oauth2/code/:registration_id.
However, it seems that only /login/oauth2/code/:registration_id is not provided.
Is there a way to use only /login/oauth2/code/:registration_id in spring security?
Your mobile app is an OAuth2 client. It should handle authentication with the authorization-server and then add Authorization header with Bearer token to the request it send to secured resource-server(s).
For authorization-server, two options:
use your "social" identity provider if it support OAuth2 but this is rarely possible, see below
put an authorization-server in front of "social" identity provider(s). This can be useful in many cases (Keycloak for instance works for all below and is in my opinion a way more mature solution than Spring's authorization-server):
you have several identity sources (Google, Facebook, etc)
you need to add role management to your users (some users need to be identified as moderators or whatever elevated privileges)
you have non OAuth2 identity sources (database or corporate LDAP for instance)
several identity sources but some of it are not issuing OpenID JWT access-tokens
Last, the resource-server (REST API) which can completely ignore login flows (which should be handled by client). All it needs is an Authorization header with a bearer token and either a JWT decoder or an introspection endpoint. See this article for configuring such a resource-server.
I configure my resource-servers to return 401 (unauthorized) when Authorization header is invalid or missing, and not 302 (redirect to login):
in my opinion, this is client responsibility to handle end-user login and also know which URIs require an Authorization (and from which issuer): if your app consumes resources from different providers (for instance Facebook plus Google Maps plus your own Spring services) it must know what access-token to associate with which request.
you can end with cases where your resource-server is exposed to several clients with different issuers. In that case, do you really want to put some code in your resource-servers to figure out to which authorization-server redirect depending on the client ID?
I don't know the side effect, but I found a way to work.
package com.example.demo.config.security;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.StreamSupport;
#Component
#RequiredArgsConstructor
public class CustomOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
private static final char PATH_DELIMITER = '/';
private final InMemoryClientRegistrationRepository clientRegistrationRepository;
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
ClientRegistration clientRegistration = resolveRegistration(request);
if (clientRegistration == null) {
return null;
}
String redirectUriAction = getAction(request, "login");
String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
return OAuth2AuthorizationRequest.authorizationCode()
.attributes((attrs) -> attrs.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()))
.clientId(clientRegistration.getClientId())
.redirectUri(redirectUriStr)
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.state(request.getParameter("state"))
.scopes(clientRegistration.getScopes())
.build();
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
return loadAuthorizationRequest(request);
}
private ClientRegistration resolveRegistration(HttpServletRequest request) {
return StreamSupport.stream(clientRegistrationRepository.spliterator(), false)
.filter(registration -> {
return Optional
.ofNullable(UriComponentsBuilder
.fromHttpUrl(registration.getRedirectUri())
.build().getPath())
.orElse("")
.equals(request.getRequestURI());
})
.findFirst()
.orElse(null);
}
private String getAction(HttpServletRequest request, String defaultAction) {
String action = request.getParameter("action");
if (action == null) {
return defaultAction;
}
return action;
}
private static String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration,
String action) {
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
// #formatter:off
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build();
// #formatter:on
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
String host = uriComponents.getHost();
uriVariables.put("baseHost", (host != null) ? host : "");
// following logic is based on HierarchicalUriComponents#toUriString()
int port = uriComponents.getPort();
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
String path = uriComponents.getPath();
if (StringUtils.hasLength(path)) {
if (path.charAt(0) != PATH_DELIMITER) {
path = PATH_DELIMITER + path;
}
}
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("action", (action != null) ? action : "");
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri()).buildAndExpand(uriVariables)
.toUriString();
}
}

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);

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

Grails Spring security for different form of authentication

I am currently using the grails and Spring security to implement the authenitcation. Not doing much customization , using the out of the box
and everything works fine for a user who lands into the auth.gsp where he will enter the username/password.
Now i have a requirement along with the normal flow, there will be a punch out from external site to our site.punch out will have username and passord provided as parameters.
In the punch out scenario user should not see the login screen and should get authenticated using the username/password provided in the url parameter.
Is this possible ?
How can I acheive both this in the same code
Yes, you can do programmatic login. For example:
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
....
AuthenticationManager authenticationManager;
....
def login(String username, String password) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(params.username, params.password);
User details = new User(params.username);
token.setDetails(details);
try {
//doing actual authentication
Authentication auth = authenticationManager.authenticate(token);
log.debug("Login succeeded!");
//setting principal in context
SecurityContextHolder.getContext().setAuthentication(auth);
return true
} catch (BadCredentialsException e) {
log.debug("Login failed")
return false
}
}
You can see some example here: http://raibledesigns.com/rd/entry/java_web_application_security_part3
Hope this helps.

Resources