I have a j2ee web application running on spring web flow using spring security. How do I change during runtime my Role saved in the session?
If it's possible, it would be something like this:
SecurityContext context = SecurityContextHolder.getContext();
Object principal = context.getAuthentication().getPrincipal();
Object credentials = context.getAuthentication().getCredentials();
GrantedAuthority[] authorities = new GrantedAuthority[1];
authorities[0] = new GrantedAuthorityImpl("MY_NEW_ROLE");
Authentication auth = new UsernamePasswordAuthenticationToken(
principal, credentials, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
Related
I am building a multi-tenant application which should enable each tenant to use its own IAM provider for the purpose of users authentication. So finally, for example, Tenant1 can use Keycloak, Tenant2 can use OneIdentity, Tenant3 any IAM provider of its choice... The application should enable registering a new tenant together with its IAM provider dynamically (at runtime). I register the OIDC clients having a ClientRegistrationRepository, like:
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(
ClientRegistrations.fromIssuerLocation("iam-provider-1-issuer-location")
.registrationId("registration-id")
.clientId("client-id")
.clientSecret("client-secret")
.build(),
ClientRegistrations.fromIssuerLocation("iam-provider-2-issuer-location")
.registrationId("registration-id")
.clientId("client-id")
.clientSecret("client-secret")
.build(),
);
}
But, the ClientRegistrationRepository doesn't provide a way to update the client registrations. Is there a way to introduce a new client registration at runtime which will be taken into consideration from Spring Security?
Redirect to /oauth2/authorization/{registrationId} in the login controller.
#GetMapping(path = "/login")
public String defaultLogin(HttpServletRequest request) {
String domainName = request.getServerName();
Tenant tenant = tenantService.findByDomainName(domainName);
return String.format("redirect:/oauth2/authorization/%s", tenant.getId());
}
And override findByRegistrationId method, and you can update ClientRegistration as you want.
#Override
public ClientRegistration findByRegistrationId(String registrationId) {
Tenant tenant = tenantService.findById(registrationId);
return ClientRegistration.withRegistrationId(registrationId)
.clientId(tenant.getClientId())
.clientSecret(tenant.getClientSecret())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email")
.clientName(registrationId)
.authorizationUri(String.format("https://%s/authorize", tenant.getAuthProviderDomain()))
.tokenUri(String.format("https://%s/oauth/token", tenant.getAuthProviderDomain()))
.userInfoUri(String.format("https://%s/userinfo", tenant.getAuthProviderDomain()))
.jwkSetUri(String.format("https://%s/.well-known/jwks.json", tenant.getAuthProviderDomain()))
.userNameAttributeName("name")
.build();
}
I have Jhipster running with Oauth2 + Keycloak.
I have a use case where I need to update user last and first name from the Jhipster React UI, so I used the Keycloak admin client via a service account to update user attributes in Keycloak.
The problem is that the information needs to be re-fetched to the OIDC token to let the user see the changes immediately. (similar issue here: https://github.com/jhipster/generator-jhipster/issues/7398 )
Is there any suggestion how to setup Spring Security to be able to re-fetch/refresh my token with the latest information form Keycloak, or any explicit call to do it?
Thanks for the answears!
So from workflow point of view I was able to solve the problem by:
Changing the data via Keycloak admin client
Change the data in the Spring Security Context
I had a wrong assumption about spring security that it validates the token data against the actual token stored in the context on every call. It turned out the spring security has no problem by changing the data in the context, so on the next login I can get a valid token what is inline with the actual data.
This is the code I was able to change the context with:
public void updateUserRole(AbstractAuthenticationToken abstractAuthenticationToken)
{
SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneByLogin)
.ifPresent(user -> {
Set<Authority> authorities = user.getAuthorities();
Authority authority = new Authority();
authority.setName(AuthoritiesConstants.USER);
authorities.remove(AuthoritiesConstants.INVITED);
authorities.add(authority);
user.setAuthorities(authorities);
this.clearUserCaches(user);
log.debug("Changed Information for User: {}", user);
});
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(AuthoritiesConstants.USER));
Map<String, Object> claims = ((OidcIdToken)((DefaultOidcUser)((OAuth2AuthenticationToken)abstractAuthenticationToken).getPrincipal()).getIdToken()).getClaims();
String userNameKey = ((OAuth2AuthenticationToken)authentication).getAuthorizedClientRegistrationId();
String tokenValue = ((OidcIdToken) ((DefaultOidcUser) ((OAuth2AuthenticationToken) abstractAuthenticationToken).getPrincipal()).getIdToken()).getTokenValue();
Instant issuedAt = ((OidcIdToken) ((DefaultOidcUser) ((OAuth2AuthenticationToken) abstractAuthenticationToken).getPrincipal()).getIdToken()).getIssuedAt();
Instant expiresAt = ((OidcIdToken) ((DefaultOidcUser) ((OAuth2AuthenticationToken) abstractAuthenticationToken).getPrincipal()).getIdToken()).getExpiresAt();
OidcIdToken oidcIdToken = new OidcIdToken(tokenValue, issuedAt, expiresAt, claims);
DefaultOidcUser user = new DefaultOidcUser(authorities, oidcIdToken, "name");
OAuth2AuthenticationToken oAuth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, userNameKey);
SecurityContextHolder.getContext().setAuthentication(oAuth2AuthenticationToken);
}
I have a multi-tenanted application with several clients, who are distinguished by subdomain:
client1.mydomain.com
client2.mydomain.com
etc
I'm using forms authentication and the ASP.NET Auth & Session cookies on the client are set for the subdomain, e.g. client1.mydomain.com. This means that if I browse to client2.mydomain.com then I'm not logged in and the browser doesn't post the client1 cookies. Which is as it should be.
However something that has been picked up by our security testing is that you can take the cookie values from client1 and use the values to create cookies for client2 (we've done this in firebug). ASP.NET accepts these cookies and thinks you're authorised on client2.
How can I configure ASP.NET so that this doesn't happen?
The forms element in web.config allows you to set domain but I can't use this as I've a multi-tenanted app. I'm setting the cookie with
FormsAuthentication.SetAuthCookie(userName, false);
but I don't see a why to limit this to subdomain.
You should add the domain name to the user data of the cookie. To do this you have to switch to another cookie api:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
... other parameters ..., domain );
HttpCookie cookie = new HttpCookie( FormsAuthentication.FormsCookieName );
cookie.Value = FormsAuthentication.Encrypt( ticket );
Response.SetCookie( cookie );
Then, in your global application class have an event handler that fires after the identity is established for the request. In the handler, verify that the domain name in the cookie is equal to a domain of the current request:
public void Application_PostAuthorizeRequest( object sender, EventArgs e )
{
HttpApplication app = sender as HttpApplication;
HttpContext ctx = app.Context;
if ( ctx.User.Identity.IsAuthenticated )
{
// current domain
string currentDomain = ctx.Request.Url.... // get the domain
// domain from cookie
FormsIdentity id = (FormsIdentity)ctx.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
string cookieDomain = ticket.UserData;
if ( currentDomain != cookieDomain )
throw new Exception( "break the execution of the current request" );
...
This check will validate if the cookie was issued for the current domain or rather someone tries to reuse cookies between different domains.
I have Owin configured to issue both a token and cookie upon authentication:
public void Configuration(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
//AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "MyCookie",
LoginPath = new PathString("/app/index.html#/login"),
};
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new MyAuthorizationServerProvider(),
};
var oAuthBearerOptions = new OAuthBearerAuthenticationOptions
{
};
// Must be registered in this order!
app.UseCookieAuthentication(cookieOptions);
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
}
This works fine - it issues both the Bearer token for my SPA to call my API and the cookie so my old school MVC pages can be logged in too.
But if I register the OAuth server before declaring that I want to use CookieAuth, no cookie is issued. In other words if I do this, it doesn't work:
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseCookieAuthentication(cookieOptions);
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
Also, if I uncomment this line, it also doesn't issue the cookie:
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
My question is why does the order of registration with Owin matter? And why does setting the AuthenticationType in the cookie as "ApplicationCookie" also make it fail?
I'm not familiar with the UseOAuthAuthorizationServer() middleware, but I assume it works the same as other external authentication middleware (e.g. the Google middleware).
An authentication middleware that redirects to an external source for authentication, will only be used once, at the start, of each browsing session. It will then defer the authentication of the up-coming requests to a cookie authentication that maintains the session. This is good, because it means the overhead of the external authentication is only done once for each session.
A middleware that wants to set a cookie does typically not do it itself. Instead it sets a property in the Owin context with an AuthenticationResponseGrant. The grant is then processed by the cookie middleware that extracts the identity and sets the cookie.
For this to work:
The cookie handler must be registered before the external authentication middleware in the pipeline.
The authentication type in the AuthenticationResponseGrant must match the type of the cookie middleware.
So changing the order of the registration violates 1. and excluding the authentication type violates 2.
I have written an in depth blog post about it if you want more details.
Does Spring Security gives any such API where I can pass username & password and it will return either Authentication Object for successful authentication or AuthenticationCredentialsNotFoundException for unsuccessful authentication?
Let me elaborate my requirements:
Our application has a HTTP API(say, /createXXX.do) and the client is hitting this with username, password & other parameters.
Now I want to authenticate + authorize this access (coming from HTTP Hits to my application).
My planned design is like below:
a) I will not restrict access of my HTTP API context(i.e. /createXXX.do)
b) Once the request reached my doGet()/doPost(), I will retrieve the username & password from request and want to use some spring security API like below:
Authentication validateXXXXX(String username, String password)
throws AuthenticationCredentialsNotFoundException;
c) so that this above API internally push these username/password to the existing spring security chain and return me the Authentication Object for successful authentication or AuthenticationCredentialsNotFoundException for unsuccessful authentication.
d) For unsuccessful authentication, I will catch AuthenticationCredentialsNotFoundException and return the HttpServletResponse with AUTHENTICATION_ERROR code.
e) and for successful authetication, based on authiories from Authentication Object, I will allow or return the HttpServletResponse with AUTHORIZATION_ERROR code.
Can anyone know about such spring security API?
Any pointers/suggestion will be highly appreciated.
Thanks.
If you have just one authentication source (only LDAP or only DB) you can configure some implementation of org.springframework.security.authentication.AuthenticationProvider in your security context. Then you can use it:
User user = new User(login, password, true, true, true, true, new ArrayList<GrantedAuthority>());
Authentication auth = new UsernamePasswordAuthenticationToken(user, password,new ArrayList<GrantedAuthority>());
try {
auth = authenticationProvider.authenticate(auth);
} catch (BadCredentialsException e) {
throw new CustomBadCredentialsException(e.getMessage(), e);
}
// but your need to push authorization object manually
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
It is "low level" manipulation. You can use element from Spring Security namespace. It can provide login controller, even login form for you (and it can handle this situation automatically).