Spring oauth2 authorization server add roles user in token - spring-security

I use spring-security-oauth2-authorization-server (v. 0.2.0) for implement my authorization-server.
I would like user roles to be in the token, is it possible to add them? like?
Thanks

I solved the problem by adding the following bean
#Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (context.getTokenType() == OAuth2TokenType.ACCESS_TOKEN.getValue()) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim("user-authorities", authorities);
}
};
}

It should be == check with ACCESS_TOKEN not the value.
#Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (context.getTokenType() == OAuth2TokenType.ACCESS_TOKEN) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim("user-authorities", authorities);
}
};
}

Related

How can I map 'scope' values to Identity Claims?

I've specified an Authorization Policy that requires the scope my_custom_value, e.g.
services.AddAuthorization(AuthConfig.GetAuthorizationOptions);
// ...
public static void GetAuthorizationOptions(AuthorizationOptions options)
{
options.AddPolicy("MyPolicy", policy =>
{
policy.RequireScope("my_custom_value");
});
Requests for endpoints that are protected by MyPolicy are failing because the Principal doesn't contain any scopes
I can see that my auth token has the following scopes:
"scope": [
"openid",
"profile",
"my_custom_value",
"offline_access"
],
It appears these are not being mapped to the Principal's claims. When I inspect the Claims later when the user attempts to access a protected endpoint, there are no scopes.
policy.RequireAssertion(context =>
{
if (context.User.HasClaim(c => c.Type == "scope")) // <-- always false
{
if (context.User.HasClaim(c => c.Value == "my_custom_value"))
{
return true;
}
}
Why are the scopes not being mapped? What do I need to do to map them?
For reference, I've tried it with
options.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Scope, "scope");
options.Scope.Add("my_custom_value");
Am I supposed to implement a custom IProfileService to include the scopes in the OnUserInformationReceived event?
When doing oidc auth using MVC only the IdentityToken claims are mapped to the ClaimsPrincipal. I couldn't figure out a way to map or include access tokens claims to the ClaimsPrincipal.
I ended up writing an authorization handler that validates the access token and performs required claim checks. I assume you read about authorization policies in asp.net 5.0.
public class AccessTokenAuthorizationHandler : AuthorizationHandler<AccessTokenRequirement> {
readonly IOptionsMonitor<OpenIdConnectOptions> _openIdConnectOptions;
readonly ILogger<AccessTokenAuthorizationHandler> _logger;
readonly IOptions<OpenIdOptions> _openIdOptions;
public AccessTokenAuthorizationHandler(
ILogger<AccessTokenAuthorizationHandler> logger,
IOptionsMonitor<OpenIdConnectOptions> openIdConnectOptions,
IOptions<OpenIdOptions> openIdOptions) {
_logger = logger;
_openIdConnectOptions = openIdConnectOptions;
_openIdOptions = openIdOptions;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AccessTokenRequirement requirement) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (requirement == null) {
throw new ArgumentNullException(nameof(requirement));
}
if (context.Resource is Microsoft.AspNetCore.Mvc.ActionContext actionContext) {
ClaimsPrincipal principal = await GetAccessTokenPrincipal(actionContext.HttpContext).ConfigureAwait(false);
// verify your requirement
if (condition met) {
context.Succeed(requirement);
}
}
}
private async Task<ClaimsPrincipal> GetAccessTokenPrincipal(HttpContext httpContext) {
if (httpContext == null) {
return null;
}
String accessToken = await httpContext.GetUserAccessTokenAsync().ConfigureAwait(false);
if (!String.IsNullOrWhiteSpace(accessToken)) {
try {
TokenValidationParameters validationParameters = await BuildValidationParameters();
return new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var rawValidatedToken);
}
catch (SecurityTokenValidationException validationException) {
_logger.LogWarning(validationException, "Access token not valid.");
}
catch (Exception ex) {
_logger.LogError(ex, "Access token could not be validated.");
}
}
return null;
}
private async Task<TokenValidationParameters> BuildValidationParameters() {
var options = _openIdConnectOptions.Get(OpenIdConnectDefaults.AuthenticationScheme);
var discoveryDocument = await options.ConfigurationManager.GetConfigurationAsync(CancellationToken.None);
var signingKeys = discoveryDocument.SigningKeys;
var validationParameters = new TokenValidationParameters {
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateIssuer = true,
ValidIssuer = options.Authority,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = signingKeys,
ValidateLifetime = true,
ValidateAudience = true,
ValidAudience = "your audience",
ValidateActor = false,
ValidTypes = new String[] { "at+jwt" },
ClockSkew = TimeSpan.FromMinutes(2),
};
return validationParameters;
}
}
I am not happy i had to do it this way, though i think it is done properly. To retrieve the access token i am using nuget package IdentityModel.AspNetCore, Version=3.0.0.0
I don't understand why not more people have this problem. Of course if your app consumes data from an api you pass on the access token, and there the access token becomes the claims principal. But if your mvc app performs direct database access (and might be later extracted to an api) you need to somehow be able to check claims of the access token. Maybe we have some conceptual misunderstanding...
Regarding the profile service. I think trying to include access token claims into the identity token would not be the correct approach. I think it wouldn't even be possible because you have no information about requested scopes when the service is called for the identity token.

Grails spring switch user

I have problem with j_spring_security_switch_user, as I only can switch between users with the role ROLE_SWITCH_USER.
Can I change it so it can switch to users with the ROLE_USER from a user with the role ROLE_SWITCH_USER?
I got it fixed by:
Create file MySwichUserFilter.groovy:
class MySwichUserFilter extends SwitchUserFilter {
protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
Authentication switchTo = super.attemptSwitchUser(request);
SecurityContextHolder.getContext().getAuthentication();
return switchTo;
}
}
Correct the resources.groovy
beans = {
...
switchUserProcessingFilter(MySwichUserFilter){
userDetailsService = ref('userDetailsService')
switchUserUrl = "/j_spring_security_switch_user"
exitUserUrl = "/j_spring_security_exit_user"
targetUrl = conf.successHandler.defaultTargetUrl
}
...
}

Bearer Token Authentication with ASP.net API

I am looking into using an ASP.net web API to set up request authentication with a bearer token. When you use the OWIN server middle-ware, where is the encryption key coming from? How would the server revoke a token that has not expired?
OWIN ServerMiddleware's default Tiken data protection approach is using DPAPI (Data Protection API)
For revoking tokens at the server side, Token Store need to be implemented. You can use AccessTokenProvider.Create to create and store Token.
Here is an example for such scenario. Take this as an example code snippets.
Register in Startup.cs
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AuthorizeEndpointPath = new PathString("/Authorize"),
TokenEndpointPath = new PathString("/Token"),
ApplicationCanDisplayErrors = true,
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizationCodeProvider = new MyAuthenticationTokenProvider(TokenType.Code),
AccessTokenProvider = new MyAuthenticationTokenProvider(TokenType.Access),
RefreshTokenProvider = new MyAuthenticationTokenProvider(TokenType.Refresh),
AuthorizationCodeFormat = new MyFormatProvider("MyAudiences"),
AccessTokenFormat = new MyFormatProvider("MyAudiences"),
RefreshTokenFormat = new MyFormatProvider("MyAudiences"))
});
}
Provide Encryption: This is based on the JwtFormat in the Katana project. The JwtFormat.protect() method is still not supported. So you need to create your own implementation.
//You need to manage your Key in this class
public class MyFormatProvider: ISecureDataFormat<AuthenticationTicket>
{
public MyFormatProvider(string allowedAudiences)
{
}
public string Protect(AuthenticationTicket data)
{
return "encrypted";
}
public AuthenticationTicket Unprotect(string protectedText)
{
return new AuthenticationTicket(new System.Security.Claims.ClaimsIdentity(), new AuthenticationProperties());
}
}
Token Provider
public enum TokenType { Code,Access,Refresh }
public class MyAuthenticationTokenProvider : AuthenticationTokenProvider
{
TokenType tokenType = TokenType.Access;
public MyAuthenticationTokenProvider(TokenType tokenType)
{
}
public override void Create(AuthenticationTokenCreateContext context)
{
/*Create Token, Store Token and Tiket info*/
context.SetToken("MyToken");/*This will call Your MyFormatProvider internally*/
base.Create(context);
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
/*retrieve Token and Tiket info to process*/
base.Receive(context);
}
}

Programmatically login from a link in spring security

i am trying to automatically authorization without login in spring security. The user would be authorized by clicking a link in a website.
I have a class UserLoginService that called from spring-security xml file like this;
<authentication-manager>
<authentication-provider user-service-ref="userLoginService" >
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="userLoginService"
class="tr.com.enlil.formdesigner.server.guvenlik.UserLoginService">
</beans:bean>
UserLoginService class;
public class UserLoginService implements UserDetailsService {
private static Logger logger = Logger.getLogger(InitServlet.class);
#Autowired
private IKullaniciBusinessManager iKullaniciBusinessManager;
/**
* {#inheritDoc}
*/
#Override
public UserDetails loadUserByUsername(String username) {
try {
Kullanici kullanici = new Kullanici();
kullanici.setKullaniciAdi(username);
Kullanici kullaniciBusinessManager = iKullaniciBusinessManager.getirKullaniciAdinaGore(kullanici);
User user = new User();
if (kullaniciBusinessManager != null && kullaniciBusinessManager.getAktifmi()) {
user.setUsername(kullaniciBusinessManager.getKullaniciAdi());
user.setPassword(kullaniciBusinessManager.getSifre());
user.setKullanici(kullaniciBusinessManager);
List<String> yetkiListesi = new ArrayList<String>();
List<GrantedAuthority> grandAuthorities = new ArrayList<GrantedAuthority>();
//TODO yetkilerle alakalı birşey yapmak gerekebilir.
for (String yetki : yetkiListesi) {
GrantedAuthorityImpl g = new GrantedAuthorityImpl(yetki);
grandAuthorities.add(g);
}
user.setAuthorities(grandAuthorities);
}
return user;
} catch (Exception e) {
logger.error("Kullanici alinirken hata olustu!!", e);
}
return null;
}
public static void autoLogin(User user, HttpServletRequest request, AuthenticationManager authenticationManager) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword(), user.getAuthorities());
// generate session if one doesn't exist
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
// setting role to the session
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
}
}
I found autoLogin method from Make Programmatic login without username/password?. But i dont know, from where can i call this method and will this code help me.
Thanks in advance.
You will have to create your own implementation of AbstractPreAuthenticatedProcessingFilter. The method getPreAuthenticatedPrincipal(HttpServletRequest request) will have the request where you can get your credentials from. You will need to return a subject if it is a valid user or null if it is not. Your implementation of UserDetailsService will transform the subject to a UserDetails object.

Acegi Security: How do i add another GrantedAuthority to Authentication to anonymous user

i give users special URL with access key in it. users accessing the public page via this special url should be able to see some additional data as compared to simple anonymous user.
i want to give some additional role to anonymous user based on parameters provided in request so i can do something like this in my template:
<#sec.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER, ROLE_INVITED_VISITOR">
...some additional stuff for invited user to see
</#sec.authorize>
currently i'm implementing Spring's OncePerRequestfilter:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if (null != request.getParameter("accessKey")) {
if(isValid(request.getParameter("accessKey"))) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//how do i add additional roles to authenticated (potentially anonymous) user?
}
}
}
Why not just create a wrapper class that delegates to the original, but adds on a couple of extra GrantedAuthorities:
public class AuthenticationWrapper implements Authentication
{
private Authentication original;
private GrantedAuthority[] extraRoles;
public AuthenticationWrapper( Authentication original, GrantedAuthority[] extraRoles )
{
this.original = original;
this.extraRoles = extraRoles;
}
public GrantedAuthority[] getAuthorities()
{
GrantedAuthority[] originalRoles = original.getAuthorities();
GrantedAuthority[] roles = new GrantedAuthority[originalRoles.length + extraRoles.length];
System.arraycopy( originalRoles, 0, roles, 0, originalRoles.length );
System.arraycopy( extraRoles, 0, roles, originalRoles.length, extraRoles.length );
return roles;
}
public String getName() { return original.getName(); }
public Object getCredentials() { return original.getCredentials(); }
public Object getDetails() { return original.getDetails(); }
public Object getPrincipal() { return original.getPrincipal(); }
public boolean isAuthenticated() { return original.isAuthenticated(); }
public void setAuthenticated( boolean isAuthenticated ) throws IllegalArgumentException
{
original.setAuthenticated( isAuthenticated );
}
}
and then do this in your filter:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
GrantedAuthority extraRoles = new GrantedAuthority[2];
extraRoles[0] = new GrantedAuthorityImpl( "Role X" );
extraRoles[1] = new GrantedAuthorityImpl( "Role Y" );
AuthenticationWrapper wrapper = new AuthenticationWrapper( auth, extraRoles );
SecurityContextHolder.getContext().setAuthentication( wrapper );
The Authentication is now replaced by your version with the extra roles. NB You may have to handle the case where the Authentication has not yet been authenticated and so its getAuthorities() returns null. (The wrapper implementation currently assumes that it will always get a non-null array from its wrapped Authentication)

Resources