Spring OAuth2 - Authorize URLs with Additional Information - spring

Spring security allows us to authorize URLs with hasAnyAuthority(), hasAnyRole(), hasRole() if we set granted authorities. If I create a custom token enhancer where I can add additional information in my token, is there a way to make authorization with the additional information?
CustomTokenEnhancer:
public final class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(
OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("company", "authorizeAPIsWithCompany");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
is it possible authorize APIs based on above additional info key, value? If not, how should I approach this idea?
e.g.:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/authorizedURL").hasCompany("authorizeAPIsWithCompany")
.....
}

I think you can do what you want without using additional information. Additional information is not needed by the OAuth protocol. It's just useful for storing descriptive information. You should be able to achieve what you want with scopes, authorities and grant types for clients and also authorities(roles) for users. You can have a look at the Oauth2 spec (https://www.rfc-editor.org/rfc/rfc6749) for any further information.
You can also have different security strategies like MethodSecurityConfig:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
#PreAuthorize("hasRole('ADMIN') OR #oauth2.clientHasRole('AUTHORIZED-MERCHANT')")
#GetMapping(value = "authorized-url")
public ResponseEntity<List<Order>> getOrders() {
return ResponseEntity.ok(orderService.getOrders());
}

Related

How to set a custom principal object during or after JWT authentication?

I've changed the way a user is authenticated in my backend. From now on I am receiving JWT tokens from Firebase which are then validated on my Spring Boot server.
This is working fine so far but there's one change which I am not too happy about and it's that the principal-object is now a org.springframework.security.oauth2.jwt.Jwt and not a AppUserEntity, the user-model, like before.
// Note: "authentication" is a JwtAuthenticationToken
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt) authentication.getPrincipal();
So, after some reading and debugging I found that the BearerTokenAuthenticationFilter essentially sets the Authentication object like so:
// BearerTokenAuthenticationFilter.java
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
// Note: authenticationResult is our JwtAuthenticationToken
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationResult);
SecurityContextHolder.setContext(context);
and as we can see, this on the other hand comes from the authenticationManager which is a org.springframework.security.authentication.ProviderManager and so on. The rabbit hole goes deep.
I didn't find anything that would allow me to somehow replace the Authentication.
So what's the plan?
Since Firebase is now taking care of user authentication, a user can be created without my backend knowing about it yet. I don't know if this is the best way to do it but I intend to simply create a user record in my database once I discover a valid JWT-token of a user which does not exist yet.
Further, a lot of my business logic currently relies on the principal being a user-entity business object. I could change this code but it's tedious work and who doesn't want to look back on a few lines of legacy code?
I did it a bit different than Julian Echkard.
In my WebSecurityConfigurerAdapter I am setting a Customizer like so:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2ResourceServer()
.jwt(new JwtResourceServerCustomizer(this.customAuthenticationProvider));
}
The customAuthenticationProvider is a JwtResourceServerCustomizer which I implemented like this:
public class JwtResourceServerCustomizer implements Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer> {
private final JwtAuthenticationProvider customAuthenticationProvider;
public JwtResourceServerCustomizer(JwtAuthenticationProvider customAuthenticationProvider) {
this.customAuthenticationProvider = customAuthenticationProvider;
}
#Override
public void customize(OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer jwtConfigurer) {
String key = UUID.randomUUID().toString();
AnonymousAuthenticationProvider anonymousAuthenticationProvider = new AnonymousAuthenticationProvider(key);
ProviderManager providerManager = new ProviderManager(this.customAuthenticationProvider, anonymousAuthenticationProvider);
jwtConfigurer.authenticationManager(providerManager);
}
}
I'm configuring the NimbusJwtDecoder like so:
#Component
public class JwtConfig {
#Bean
public JwtDecoder jwtDecoder() {
String jwkUri = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken#system.gserviceaccount.com";
return NimbusJwtDecoder.withJwkSetUri(jwkUri)
.build();
}
}
And finally, we need a custom AuthenticationProvider which will return the Authentication object we desire:
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtDecoder jwtDecoder;
#Autowired
public JwtAuthenticationProvider(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication;
Jwt jwt;
try {
jwt = this.jwtDecoder.decode(token.getToken());
} catch (JwtValidationException ex) {
return null;
}
List<GrantedAuthority> authorities = new ArrayList<>();
if (jwt.hasClaim("roles")) {
List<String> rolesClaim = jwt.getClaim("roles");
List<RoleEntity.RoleType> collect = rolesClaim
.stream()
.map(RoleEntity.RoleType::valueOf)
.collect(Collectors.toList());
for (RoleEntity.RoleType role : collect) {
authorities.add(new SimpleGrantedAuthority(role.toString()));
}
}
return new JwtAuthenticationToken(jwt, authorities);
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
}
This is working fine so far but there's one change which I am not too happy about and it's that the principal-object is now a org.springframework.security.oauth2.jwt.Jwt and not a AppUserEntity, the user-model, like before.
In my application I have circumvented this by rolling my own JwtAuthenticationFilter instead of using BearerTokenAuthenticationFilter, which then sets my User Entity as the principal in the Authentication object. However, in my case this constructs a User barely from the JWT claims, which might be bad practice: SonarLint prompts to use a DTO instead to mitigate the risk of somebody injecting arbitrary data into his user record using a compromised JWT token. I don't know if that is a big deal - if you can't trust your JWTs, you have other problems, IMHO.
I don't know if this is the best way to do it but I intend to simply create a user record in my database once I discover a valid JWT-token of a user which does not exist yet.
Keep in mind that JWTs should be verified by your application in a stateless manner, solely by verifying their signature. You shouldn't hit the database every time you verify them. Therefor it would be better if you create a user record using a method call like
void foo(#AuthenticationPrincipal final Jwt jwt) {
// only invoke next line if reading JWT claims is not enough
final User user = userService.findOrCreateByJwt(jwt);
// TODO method logic
}
once you need to persist changes to the database that involve this user.
Since
SecurityContextHolder.setContext(context);
won't work for
request.getUserPrincipal();
you may create a custom class extending HttpServletRequestWrapper
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class UserPrincipalHttpServletRequest extends HttpServletRequestWrapper {
private final Principal principal;
public UserPrincipalHttpServletRequest(HttpServletRequest request, Principal principal) {
super(request);
this.principal = principal;
}
#Override
public Principal getUserPrincipal() {
return principal;
}
}
then in your filter do something like this:
protected void doFilterInternal(HttpServletRequest request){
. . .
// create user details, roles are required
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("SOME ROLE"));
UserDetails userDetails = new User("SOME USERNAME", "SOME PASSWORD", authorities);
// Create an authentication token
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// follow the filter chain, using the new wrapped UserPrincipalHtppServletRequest
chain.doFilter(new UserPrincipalHttpServletRequest(request, usernamePasswordAuthenticationToken), response);
// all filters coming up, will be able to run request.getUserPrincipal()
}
According Josh Cummings answer in issue #7834 make configuration:
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http...
.oauth2ResourceServer(oauth2 -> oauth2.jwt(
jwt -> jwt.jwtAuthenticationConverter(JwtUtil::createJwtUser)))
...
return http.build();
}
and implement factory method, e.g:
public class JwtUtil {
public static JwtUser createJwtUser(Jwt jwt) {
int id = ((Long) jwt.getClaims().get("id")).intValue();
String rawRoles = (String) jwt.getClaims().get("roles");
Set<Role> roles = Arrays.stream(rawRoles.split(" "))
.map(Role::valueOf)
.collect(Collectors.toSet());
return new JwtUser(jwt, roles, id);
}
}
public class JwtUser extends JwtAuthenticationToken {
public JwtUser(Jwt jwt, Collection<? extends GrantedAuthority> authorities, int id) {
super(jwt, authorities);
....
}
}
Take in note, that controller's methods should inject JwtUser jwtUser without any #AuthenticationPrincipal

How check user role and get authenticated username in Spring Security JWT authentication

I'm working on Spring Security implementation with JWT authentication. I'm not sure how to check the user role and get authenticated user at method level. I saw this example on Internet:
#PostMapping("{id}")
#Secured({"ROLE_ADMIN"})
public ResponseEntity<?> save(Authentication authentication, Principal principal, #PathVariable Integer id, #RequestBody UserNewDTO dto) {
........
}
Do I need to extract the user type from the JWT token is there is other way to implement this? Using only #Secured({"ROLE_ADMIN"}) looks to me uncomplete.
Looks like this code is used to get user if session type is used, I get NPE. Do you know for JWT how I can the the user?
Github Full source: https://github.com/rcbandit111/OAuth2/blob/master/src/main/java/org/engine/rest/DataPagesController.java
There are many ways to design authority-based access to the APIs using annotations as well as security configurations based on the endpoints.
Annotations:
#Secured
#PreAuthorize
#PostAuthorize
#RolesAllowed
#PreFilter
#PostFilter
In order to use the annotations you need to enable the security configurations as follow
#Configuration
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
The prePostEnabled property enables Spring Security pre/post annotations
The securedEnabled property determines if the #Secured annotation should be enabled
The jsr250Enabled property allows us to use the #RoleAllowed annotation
#Secured & #RoleAllowed
Users who have the given role are able to execute the method. The #RoleAllowed annotation is the JSR-250’s equivalent annotation of the #Secured annotation.
#Secured({ "ROLE_ADMIN", "ROLE_SUPERADMIN" })
public ResponseEntity<?> save(...) {
...
}
#RolesAllowed({ "ROLE_ADMIN", "ROLE_SUPERADMIN" })
public ResponseEntity<?> save(...) {
...
}
#PreAuthorize & #PostAuthorize
The #PreAuthorize annotation checks the given expression before entering the method, whereas, the #PostAuthorize annotation verifies it after the execution of the method and could alter the result.
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public ResponseEntity<?> save(...) {
...
}
The major difference between #PreAuthorize & #PostAuthorize and #Secured is that #Secured does not support the SpEL (Spring Expression Language). To check more difference you may read more details here
#PreAuthorize("#username == authentication.principal.username")
public String methodX(String username) {
//...
}
#PostAuthorize("#username == authentication.principal.username")
public String methodY(String username) {
//...
}
Here, a user can invoke the methodX only if the value of the argument username is the same as the current principal's username. You can check the other possible SpEL (Spring Expression Language) customization here
You can get the more details from the here
Using configure(HttpSecurity http) and configure(WebSecurity web) method.
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) {
web
.ignoring()
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/test/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/**").hasAuthority(AuthoritiesConstants.USER)
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);
// #formatter:on
}
}
configure(WebSecurity web)
Endpoint used in this method ignores the spring security filters, security features (secure headers, csrf protection etc) are also ignored and no security context will be set and can not protect endpoints for Cross-Site Scripting, XSS attacks, content-sniffing.
configure(HttpSecurity http)
Endpoint used in this method ignores the authentication for endpoints used in antMatchers and other security features will be in effect such as secure headers, CSRF protection, etc.
You can use the hasRole(), hasAnyRole(), hasAuthority(), hasAnyAuthority() methods with the configure(HttpSecurity http). Note that with the hasRole(), hasAnyRole() method you don't need to use the ROLE_ prefix while with other two you have to use the ROLE_
To get the difference and usage you may get the details here
You can also create the utils method as follows which might be helpful.
/**
* Get the login of the current user.
*
* #return the login of the current user.
*/
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
});
}
/**
* Check if a user is authenticated.
*
* #return true if the user is authenticated, false otherwise.
*/
public static boolean isAuthenticated() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.addAll(authentication.getAuthorities());
return authorities.stream()
.noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS));
})
.orElse(false);
}
/**
* If the current user has a specific authority (security role).
* <p>
* The name of this method comes from the {#code isUserInRole()} method in the Servlet API.
*
* #param authority the authority to check.
* #return true if the current user has the authority, false otherwise.
*/
public static boolean isCurrentUserInRole(String authority) {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.addAll(authentication.getAuthorities());
return authorities.stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority));
})
.orElse(false);
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
UPDATE
#Component("userVerifier")
public class UserVerifier {
public boolean isPermitted(Authentication authentication) {
String PERMITTED_USERNAME = Arrays.asList("abc", "xyz");
return PERMITTED_USERNAME.stream.anyMatch(username -> authentication.getName().equals(username));
}
}
In security configurations we can use configure(HttpSecurity http) as follow which will invoke the isPermitted() method.
http
.authorizeRequests()
.antMatchers("/your-endpoint/{id}")
.access("#userVerifier.isPermitted(authentication)")
...
OR using the annotation as follows:
#PreAuthorize("#userVerifier.isPermitted(authentication)")
#PostMapping("{id}")
public ResponseEntity<?> save(Authentication authentication, Principal principal, #PathVariable Integer id, #RequestBody UserNewDTO dto) {
........
}
You may find more details from here and from this blog
This link explains everything about JWT authentication.
Below you can see some examples could be used as base to adapt your code:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/test")
public class TestController {
#GetMapping("/all")
public String allAccess() {
return "Public Content.";
}
#GetMapping("/user")
#PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
public String userAccess() {
return "User Content.";
}
#GetMapping("/mod")
#PreAuthorize("hasRole('MODERATOR')")
public String moderatorAccess() {
return "Moderator Board.";
}
#GetMapping("/admin")
#PreAuthorize("hasRole('ADMIN')")
public String adminAccess() {
return "Admin Board.";
}
}
I am mostly using JWT authentication and spring security together in my web applications. Here is my common practice shortly:
Verify JWT token(or query from your token store)
private Claims getClaimsFromToken(String token, String key) throws ServletException {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
Fetch user to be authenticated and its authorities(or role in your case) who owns the token.
User user = getUserFromToken(token);
List<GrantedAuthority> authorities = getGrantedAuthorities(user);
public List<GrantedAuthority> getGrantedAuthorities(User user) {
List<GrantedAuthority> result = new ArrayList<>();
for (String privilegeName : user.getAuthorities()){ // e.g. ["READ", "WRITE"]
result.add(new SimpleGrantedAuthority(privilegeName));
}
return result;
}
Create org.springframework.security.authentication.AbstractAuthenticationToken
with your user and its authorities and inject into SecurityContextHolder.
AuthenticationFilter.java:
JWTAuthenticationToken jwtAuthenticationToken = new JWTAuthenticationToken(user,
authorities);
JWTAuthenticationToken.java
public class JWTAuthenticationToken extends AbstractAuthenticationToken {
private User user;
public JWTAuthenticationToken(User user, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.user = user;
}
Use #PreAuthorize with required authority for user can access.
#PreAuthorize("hasAnyAuthority('READ')")
Get user from SecurityContextHolder if necesseray.
User User= SecurityContextHolder.getContext().getAuthentication().getUser();
Have you tried:
#PreAuthorize ("hasRole('ROLE_ADMIN')")
Edit:
To check if the user is assigned to more than one role use:
#PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')")
You can implement your own AbstractPreAuthenticatedProcessingFilter and create your principal yourself.
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
final String token = request.getHeader("YOUR_HEADER");
DecodedJWT jwt = JWT.decode(token);
// TODO create principal
}

How to extract custom Principal in OAuth2 Resource Server?

I'm using Keycloak as my OAuth2 Authorization Server and I configured an OAuth2 Resource Server for Multitenancy following this official example on GitHub.
The current Tenant is resolved considering the Issuer field of the JWT token.
Hence the token is verified against the JWKS exposed at the corresponding OpenID Connect well known endpoint.
This is my Security Configuration:
#EnableWebSecurity
#RequiredArgsConstructor
#EnableAutoConfiguration(exclude = UserDetailsServiceAutoConfiguration.class)
public class OrganizationSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final TenantService tenantService;
private List<Tenant> tenants;
#PostConstruct
public void init() {
this.tenants = this.tenantService.findAllWithRelationships();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(new MultiTenantAuthenticationManagerResolver(this.tenants));
}
}
and this is my custom AuthenticationManagerResolver:
public class MultiTenantAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {
private final AuthenticationManagerResolver<HttpServletRequest> resolver;
private List<Tenant> tenants;
public MultiTenantAuthenticationManagerResolver(List<Tenant> tenants) {
this.tenants = tenants;
List<String> trustedIssuers = this.tenants.stream()
.map(Tenant::getIssuers)
.flatMap(urls -> urls.stream().map(URL::toString))
.collect(Collectors.toList());
this.resolver = new JwtIssuerAuthenticationManagerResolver(trustedIssuers);
}
#Override
public AuthenticationManager resolve(HttpServletRequest context) {
return this.resolver.resolve(context);
}
}
Now, because of the design of org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver
which is private, the only way I can think in order to extract a custom principal is to reimplement everything that follows:
TrustedIssuerJwtAuthenticationManagerResolver
the returned AuthenticationManager
the AuthenticationConverter
the CustomAuthenticationToken which extends JwtAuthenticationToken
the CustomPrincipal
To me it seems a lot of Reinventing the wheel, where my only need would be to have a custom Principal.
The examples that I found don't seem to suit my case since they refer to OAuth2Client or are not tought for Multitenancy.
https://www.baeldung.com/spring-security-oauth-principal-authorities-extractor
How to extend OAuth2 principal
Do I really need to reimplement all such classes/interfaes or is there a smarter approach?
This is how I did it, without reimplementing a huge amount of classes. This is without using a JwtAuthenticationToken however.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver()));
}
#Bean
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver() {
List<String> issuers = ... // get this from list of tennants or config, whatever
Predicate<String> trustedIssuer = issuers::contains;
Map<String, AuthenticationManager> authenticationManagers = new ConcurrentHashMap<>();
AuthenticationManagerResolver<String> resolver = (String issuer) -> {
if (trustedIssuer.test(issuer)) {
return authenticationManagers.computeIfAbsent(issuer, k -> {
var jwtDecoder = JwtDecoders.fromIssuerLocation(issuer);
var provider = new JwtAuthenticationProvider(jwtDecoder);
provider.setJwtAuthenticationConverter(jwtAuthenticationService::loadUserByJwt);
return provider::authenticate;
});
}
return null;
};
return new JwtIssuerAuthenticationManagerResolver(resolver);
}
}
#Service
public class JwtAuthenticationService {
public AbstractAuthenticationToken loadUserByJwt(Jwt jwt) {
UserDetails userDetails = ... // or your choice of principal
List<GrantedAuthority> authorities = ... // extract from jwt or db
...
return new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
}
}

How can I access custom claims added to token using jdbc token store in authorization server in resource server?

I have added custom claims in token using TokenEnhancer, I need some of the custom claims to be available in Principal and/or authentication object.
I am using JdbcTokenStore and not JwtTokenStore.
I have gone through a couple of forum and articles but most talk about JwtTokenStore and not JdbcTokenStore.
public class AuthTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("claim1", "claimVal");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Your question has the answer within it.
JWT is used to represent claims between two parties securely. Without JWT, there is no question of claims.
That means how can you add a claim into a normal token and expect it to be read by the resource server. This token enhancer will provide the additional information to the client and won't be stored into any database and hence there is no way for the resource server to know it.
The simplest solution for your case is to use JWT. But if it is a sensitive information, maybe you should store it within the database and expose via an API which will check for the authority of the user accessing it.
Basically you could:
At the AuthorizationServer: Provide your custom TokenEnhancer
implementation and configure it via AuthorizationServerConfigurer
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenEnhancer(tokenEnhancer());
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new TestTokenEnhancer();
}
}
At the ResourceServer: Extend DefaultUserAuthenticationConverter and override extractAuthentication in which you can read the custom claim from the Map and add it to the Authentication (or your own extension of it).
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
RemoteTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(testTokenServices());
}
private ResourceServerTokenServices testTokenServices() {
tokenServices.setAccessTokenConverter(testAccessTokenConverter());
return tokenServices;
}
private AccessTokenConverter testAccessTokenConverter() {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(testUserTokenConverter());
return accessTokenConverter;
}
/**
* Retrieve additional claims that were set in the TokenEnhancer of the Authorization server during Oauth token creation.
*/
private UserAuthenticationConverter testUserTokenConverter() {
return new DefaultUserAuthenticationConverter() {
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
Authentication authentication = super.extractAuthentication(map);
if (authentication != null) {
authentication = new TestAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(), authentication.getAuthorities(),
(String) map.get("testKey"));
}
return authentication;
}
};
}
}
This thread contains related solutions.
Don't forget to delete the token from the database (table oauth_access_token) during development iterations! Otherwise you could get an "old" (not expired) token not reflecting your current custom claims.

spring security, UserDetailsService, authenticationProvider, pass word encoder.. i'm lost

first, i've read/re-read (repeat 10 times), at least 6 books on spring and spring security and have googled my brains out trying to figure it all out.
after working w/ spring for 10 years, i still find that there is so much annotation-defined, injected, component, config annotation magic going on that i have 0 confidence that i understand my applications as i should.
examples online are either xml-config, not complete, done n diff. ways, overly simplistic, using older spring, conflicting and just simply not built to handle a basic realistic use-case.
as an example, the following code is trying to handle a simple logon, authenticated to db table using an encoder for passwords.
form post includes a "client" to which one authenticates to, a persisted IP address and some url path info for deep linking post logon.
(all really basic stuff for today's single-page web apps)
i originally had this working using xml config, but javaConfig has me stuck.
i have no idea how the userDetailsService, AuthenticationManagerBuilder and PasswordEncoder interact in SecurityConfiguration. I get logon data to the service, but am not sure where or when a spring authenticationProvider is applied or if i even need one.
my User implements UserDetails and holds the required fields.
i populate those and granted authorities in my CustomUserDetailsService.
how/when/why do i need an auth.authenticationProvider(authenticationProvider()), if i check db using logon/password in my service?
my UserDetailsService seems to be executing twice now.
how does spring take the submitted password, encode it and compare to that stored in the db?
how does it know to use the same salt as that used when the p/w was created/persisted when the user was created?
why does configureGlobal() define both auth.userDetailsService and auth.authenticationProvider when authenticationProvider() also sets the userDetailsService?
why is my brain so small that i cannot make sense of this ? :)
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private ClientDAO clientDAO;
#Autowired
private UserDAO userDAO;
public UserDetails loadUserByUsername(String multipartLogon) throws UsernameNotFoundException, DataAccessException {
Boolean canAccess = false;
Long clientId = null;
String userLogon = null;
String password = null;
String id = null;
String entryUrl = null;
String ipAddress = null;
String urlParam = null;
String[] strParts = multipartLogon.split(":");
try {
userLogon = strParts[0];
password = strParts[1];
id = strParts[2];
entryUrl = strParts[3];
ipAddress = strParts[4];
urlParam = strParts[5];
} catch(IndexOutOfBoundsException ioob) { }
Client client = new Client();
if (!"".equals(id)) {
clientId = IdUtil.toLong(id);
client = clientDAO.getClient(clientId);
}
//BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//String encodedPassword = passwordEncoder.encode(password);
//String encodedPassword = "$2a$22$6UiVlDEOv6IQWjKkLm.04uN1yZEtkepVqYQ00JxaqPCtjzwIkXDjy";
User user = userDAO.getUserByUserLogonPassword(userLogon, password); //encodedPassword?
user.isCredentialsNonExpired = false;
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (UserRole userRole : userDAO.getUserRolesForUser(user)) {
if (userRole.getRole().getActiveStatus()) {
authorities.add(new SimpleGrantedAuthority(userRole.getRole().getRoleName()));
user.isCredentialsNonExpired = true;
}
}
user.setAuthorities(authorities);
user.setPassword(password); //encodedPassword?
user.setUsername(user.getUserLogon());
user.isAccountNonExpired = false;
user.isAccountNonLocked = false;
List<ClientUser> clientUsers = clientDAO.getClientUsersForUser(user);
for (ClientUser clientUser : clientUsers) {
if (clientUser.getClient().getClientId().equals(client.getClientId())) {
canAccess = true;
break;
}
}
user.isEnabled = false;
if (user.getActiveStatus() && canAccess) {
user.isAccountNonExpired = true;
user.isAccountNonLocked = true;
user.isEnabled = true;
Session session = userDAO.getSessionForUser(user);
if (session == null) { session = new Session(); }
session.setUser(user);
session.setDateLogon(Calendar.getInstance().getTime());
session.setClient(client);
session.setEntryUrl(entryUrl);
session.setUrlParam(urlParam);
session.setIPAddress(ipAddress);
session.setActive(true);
userDAO.persistOrMergeSession(session);
}
return user;
}
}
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/conv/a/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_COURT_ADMIN')")
.antMatchers("/conv/u/**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN') or hasRole('ROLE_COURT_ADMIN')")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/conv/common/logon")
.usernameParameter("multipartLogon")
.loginProcessingUrl("/conv/common/logon")
.defaultSuccessUrl("/conv/")
.failureUrl("/conv/common/logon?error=1")
.and()
.logout()
.logoutUrl("/conv/common/logout")
.logoutSuccessUrl("/conv/")
.permitAll()
.and()
.rememberMe()
.key("conv_key")
.rememberMeServices(rememberMeServices())
.useSecureCookie(true);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/common/**")
.antMatchers("/favicon.ico");
}
#Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("conv_key", customUserDetailsService);
rememberMeServices.setCookieName("remember_me_cookie");
rememberMeServices.setParameter("remember_me_checkbox");
rememberMeServices.setTokenValiditySeconds(2678400); //1month
return rememberMeServices;
}
}
my User implements UserDetails and holds the required fields. i
populate those and granted authorities in my CustomUserDetailsService.
how/when/why do i need an
auth.authenticationProvider(authenticationProvider()), if i check db
using logon/password in my service?
I think what you want is:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
The userDetailsService method is a shortcut for creating DaoAuthenticationProvider bean! You should not need both, its just two different ways to configure the same thing. The authenticationProvider method is used for more custom setups.
how does spring take the submitted password, encode it and compare to
that stored in the db? how does it know to use the same salt as that
used when the p/w was created/persisted when the user was created?
If you are using BCrypt, the salt is stored in the encoded password value. The salt is the first 22 characters after the third $ (dollar) sign. The matches method is responsible for checking the password.
why does configureGlobal() define both auth.userDetailsService and
auth.authenticationProvider when authenticationProvider() also sets
the userDetailsService?
See above. This is likely why user details are loaded twice.
Update: It is weird that you get password and other details into your UserDetailsService. This should only load user based on username, something like:
User user = userDAO.getUserByUserLogonPassword(userLogon);
The returned User object should contain the encoded (stored) password, not the entered password. Spring Security does the password checking for you. You should not modify the User object in you UserDetailsService.
Wow, ok that's a lot of questions. I'll speak to this one:
"I have no idea how the userDetailsService, AuthenticationManagerBuilder and PasswordEncoder "
The UserDetailsService sets up the User which you can access from Spring. If you want more user information stored in the context on the user, you need to implement your own user and set that up with your custom user details service. e.g.
public class CustomUser extends User implements UserDetails, CredentialsContainer {
private Long id;
private String firstName;
private String lastName;
private String emailAddress;
....
And then, in your custom UserDetailsService, you set the properties:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DatabaseEntity databaseUser = this.userRepository.findByUsernameIgnoreCase(username);
customUser customUser = databaseUser.getUserDetails();
customUser.setId(databaseUser.getId());
customUser.setFirstName(databaseUser.getFirstname());
.....
The password encoder, is the mechanism Spring uses to compare the plain-text password to the encrypted hash in your database. You can use the BCryptPasswordEncoder:
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
Aside from passing that to your auth provider, you do need to do any more.
Finally, configureGlobal is where you wire things up. You define your user details service Spring is to use and the authentication provider.
In my case, I use a custom authentication provider to limit failed login attempts:
#Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
And then I wire everything up:
#Autowired
#Qualifier("authenticationProvider")
AuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LimitLoginAuthenticationProvider provider = (LimitLoginAuthenticationProvider)authenticationProvider;
provider.setPasswordEncoder(passwordEncoder);
auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder);
auth.authenticationProvider(authenticationProvider);
}

Resources