I am using a spring security OAuth2 using JWT tokens for some time but now I need to add 2 user defined values to the JWT token.
So when I added an additional set of parameters to the request /oauth/token?grant_type=client_credentials&user_value=1234567890.
The user_value above was for demonstrative purposes. I can trace it all the way into my CustomTokenEnhancer (I connected this as a way to pass this information all the way through). All the request parameters are visible through OAuth2Authentication authentication which is passed to my CustomTokenEnhancer.
Now I can add this information to the additional information which I see returned to me as part of the token request. See below.
{
"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicGhpLWFwaSJdLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwib3JnYW5pemF0aW9uIjoicGhpLXVzZXJtZ3RuIiwidXNlcl90b2tlbiI6IjEyMzQ1Njc4OTAiLCJleHAiOjE0ODczNjc2NzEsImF1dGhvcml0aWVzIjpbIlJPTEVfQ0xJRU5UIl0sImp0aSI6IjFlZDMzZTAxLTc1ZGUtNDNjZC1hMzk2LTFkMzk2N2Y1NDQ5OCIsImNsaWVudF9pZCI6InBoaS11c2VyIn0.p628BNaaGljypEcGXZMkstNeTN-221qzzNQQ0npxDLTszWaXkgXqsBnBbKf9XMEtWTeCQkIszC9ne1Ei2X5IWTskhLT9Rko-8K7Jq-mXUc6HJZW-3tGV5rRer8Eyyw1wysW9Jiyp7sPkN-TIx12A70f_LHm6PrRR4ECppHWADs-2DvYA30p8omT1_RTt2WlqC40mopUN2TBPkb1WulVpOUEpcP358Ox8oVP8VQRSkLGZKB_b0KZAK9KGjLg6WNh8RghZaBuYuJQpITe_0XEBs_JfwrHhcK1IGaoYwSS7IGp3Cima9OMljdzayDKRqlfSl3WhaBuFmD1S37p-OVQL0A",
"token_type":"bearer",
"expires_in":8967,
"scope":"read write",
"user_value":"1234567890",
"jti":"1ed33e01-75de-43cd-a396-1d3967f54498"
}
But I don't want these values to be visible this way. I want them to be added to the encrypted token.
I spent some time looking and it isn't clear how i actually add that. This should be possible, shouldn't it?
Inside your own TokenEnhancer you have to encode it again:
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
// Generate additional Information [...]
// Write it to the token
((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(addInfo);
// Encode Token to JWT
String encoded = super.encode(accessToken, authentication);
// Set JWT as value of the token
((DefaultOAuth2AccessToken) accessToken).setValue(encoded);
return accessToken;
}
You could solve this with the JwtHelper methods, but I just extended JwtAccessTokenConverter, so I could just use encode and decode.
When instantiating your Token enhancer, you have to add the keystore information:
private CustomTokenEnhancer jwtCustomEnhancer() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "***".toCharArray());
CustomTokenEnhancer converter = new CustomTokenEnhancer();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt"));
return converter;
}
I did something similar passing the value as a granted authority with help of user details service (not the token enhancer). At the client side, I wrote an extractor to retrieve the values from the principal injected by spring as type OAuth2Authentication. The following code is in Scala, but you may easily adapt to Java:
/**
* Mix-in to implicitly extract entity or identity from the principal.
*/
trait AuthorityExtractor {
def _contextName(implicit principal: OAuth2Authentication) = id(principal, "CONTEXT_")
def _entityId(implicit principal: OAuth2Authentication) = id(principal, "ENTITY_ID_")
def _userId(implicit principal: OAuth2Authentication) = id(principal, "USER_ID_")
def _identityId(implicit principal: OAuth2Authentication) = id(principal, "SELF_ID_")
private def id(principal: OAuth2Authentication, prefix: String) = {
import collection.JavaConversions._
principal
.getAuthorities
.filter(_.toString.startsWith(prefix))
.map(_.toString.substring(prefix.length))
.headOption.getOrElse("")
}
}
I extend JwtAccessTokenConverter class like that:
public class FooJwtAccessTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken fooAccessToken = new DefaultOAuth2AccessToken(accessToken);
fooAccessToken.getAdditionalInformation().put("foo_property", "foo");
return super.enhance(scaAccessToken, authentication);
}
In my AuthotizationServerConfig I create this:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
ScaJwtAccessTokenConverter accessTokenConverter = new ScaJwtAccessTokenConverter();
accessTokenConverter.setSigningKey("familia-mgpe"); // Parte da string de validação do token JWT.
return accessTokenConverter;
}
Related
I have a page to access with form post request (webview page for mobile apps). My application can't have a login form but I need to secure it. Mobile applications will call this webview page with authentication parameters such as (email/password). I need to call third-party api with given authentication parameters and decide it was authenticated or not. Which approach should I use for my scenarios ?
If it possible to pass authentication parameters in the Authorization header, you can enable http basic authentication in your application:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
Otherwise, you can implement your own security filter to create a UsernamePasswordAuthenticationToken (or any other class implementing Authentication) instance from your specific authentication parameters and pass to AuthenticationManager; but in the case of another class, you need to make the authentication provider below support it by overriding the public boolean supports(Class<?> authentication) method.
Then implement a custom AuthenticationProvider that will delegate authentication to the third-party API, e.g.:
public class RestAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String AUTH_URL = "http://third-party-service/authentication/basic";
private RestTemplate restTemplate;
public RestAuthenticationProvider() {
this.restTemplate = new RestTemplate();
}
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
if (!authentication.getCredentials().toString().equals(userDetails.getPassword())) {
this.logger.debug("Authentication failed: invalid credentials");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
#Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
String password = authentication.getCredentials().toString();
try {
ResponseEntity<String> authenticationResponse = authenticate(username, password);
if (authenticationResponse.getStatusCode().value() == 401) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
return createUser(authenticationResponse.getBody());
} catch (BadCredentialsException ex) {
throw ex;
} catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
private ResponseEntity<String> authenticate(String username, String password) {
HttpEntity entity = new HttpEntity(createHeaders(username, password));
return restTemplate.exchange(AUTH_URL, HttpMethod.GET, entity, String.class);
}
private HttpHeaders createHeaders(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
String authorization = username + ":" + password;
String basic = Base64.getEncoder().encodeToString(authorization.getBytes());
headers.set("Authorization", "Basic " + basic);
return headers;
}
private UserDetails createUser(String json) {
return null; // TODO: Implement
}
}
And finally, make Spring Security to use your provider:
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new RestAuthenticationProvider())
.eraseCredentials(false);
}
I need to call third-party api with given authentication parameters
and decide it was authenticated or not
Assuming you need to use username / password to send to 3rd party, when you first login into the app, you can create a long lived token in back-end and sent it to the app to store in secure store. Later when you want to load the protected webview, send this token along with the request (in header or body via javascript) and in the server side pick the user / password corresponding to the token and authenticate with 3rd party.
This way you will never need to store password on client side and you can manually make the token at backend inactive too. If you like to go standard way, then you may take a look at Password Grant of OAuth 2 / OpenID Connect / . With the correct infrastructure in place you can get access tokens during login process and use that for your protected page. Spring Security has support for this flow - you can take a look here.
I am using a SignalRConnectionInfo input binding in an Azure function. In this binding I need to provide the userId of the current principal, however by default the Azure function only supports the use of pre-defined headers from App Service Authentication, as outlined here, and in the below example:
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo(HubName = "foo", UserId = "{headers.x-ms-client-principal-id}")] SignalRConnectionInfo connectionInfo)
{
return connectionInfo;
}
As I am not using App Service authentication, and do not wish to, this is not suitable for my needs.
What I do currently have is a JWT which the user provides to an API we host in another App Service in order to authenticate the request and identify themselves. How can I amend the UserId property of the SignalRConnectionInfo binding to retrieve the UserId from the claims within that JWT?
AFAIK there isn't a way to extract information from a JWT using binding expressions.
Instead, you will have to use Runtime Binding to first extract the information from the JWT and then use it in the binding to get the SignalR Connection Information.
Here's a functional example where the JWT is retrieved from the Authorization header, validated, then applied to the SignalRConnectionInfo attribute.
[FunctionName("Negotiate")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "negotiate")] HttpRequest req,
IBinder binder)
{
if (req.Headers.ContainsKey("Authorization"))
{
var principal = TryGetPrincipal(req.Headers["Authorization"].ToString());
if (principal != null)
{
var connectionInfo = await binder.BindAsync<SignalRConnectionInfo>(new SignalRConnectionInfoAttribute
{
HubName = _hubName,
UserId = principal.FindFirst(ClaimTypes.NameIdentifier).Value
});
return new OkObjectResult(connectionInfo);
}
}
return new UnauthorizedResult();
}
public ClaimsPrincipal TryGetPrincipal(string token) {
// implementation varies based on authorization type...
}
In our ASP .NET Core 2.0, Web API, when the user logs in, we generate a GUID and return that to the user after storing it in database. What is the best practice to validate this token when the user submits a request to a controller having Authorize attribute on it.
Should I override AuthorizeAttribute.OnAuthorization and put my custom logic in there ? or is there any other place where I should place my custom logic ?
Thanks in advance.
In ASP .NET Core 2.0 you can write you own Middleware to validate token. You can see this video as exapmle - https://www.youtube.com/watch?v=n0llyujNGw8.
Summarily:
1. Create TokenMiddleware:
public class TokenMiddleware
{
// always should be RequestDelegate in constructor
private readonly RequestDelegate _next;
public TokenMiddleware(RequestDelegate next)
{
_next = next;
}
// always should be defiened Invoke or InvokeAsync with HttpContext and returned Task (You can also inject you services here - for example DataContext)
public async Task InvokeAsync(HttpContext context, DataContext dataContext)
{
var validKey = true;
// than you logic to validate token
if (!validKey)
{
context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
// if validm than next middleware Invoke
else
{
await _next.Invoke(context);
}
}
}
// Extension to IApplicationBuilder (to register you Middleware)
public static class TokenExtensions
{
public static IApplicationBuilder UseTokenAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TokenMiddleware>();
}
}
Registred you Middleware in Startup:
app.UseTokenAuth();
Question was made long time ago, but for people that might stumble upon it, here is the way I did it, taking advantage of authentication and authorization middlewares. The question doesn't have details about the way the token is passed in the request but I am assuming a standard Authorization header.
Create a custom AuthenticationHandler
MyCustomTokenHandler.cs
public class MyCustomTokenHandler: AuthenticationHandler<AuthenticationSchemeOptions>
{
public MyCustomTokenHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.NoResult();
}
if (!AuthenticationHeaderValue.TryParse(Request.Headers["Authorization"], out AuthenticationHeaderValue? headerValue))
{
return AuthenticateResult.NoResult();
}
if (!Scheme.Name.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
{
return AuthenticateResult.NoResult();
}
if (headerValue.Parameter == null)
{
return AuthenticateResult.NoResult();
}
//The token value is in headerValue.Parameter, call your db to verify it and get the user's data
var claims = new[] { new Claim(ClaimTypes.Name, "username found in db") };
//set more claims if you want
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Register the handler and enable authorization
Program.cs
builder.Services.AddAuthentication("Bearer").AddScheme<AuthenticationSchemeOptions, MyCustomTokenHandler>("Bearer", null);
//...
var app = builder. Build();
app.UseAuthentication();
app.UseAuthorization();
Most of the code is inspired by this blog post: https://joonasw.net/view/creating-auth-scheme-in-aspnet-core-2
We are using Spring 4.3.9.RELEASE and Spring Security 4.2.3.RELEASE, so these are some of the latest versions we have seen. We have a RESTful (spring-mvc) backend where we are using Spring Web Security for roles-based access to the API's.
We have a controller that looks like this:
#RequestMapping(value = "/create", method = RequestMethod.POST, produces = "application/json", headers = "content-type=application/json")
public #ResponseBody MyObjectEntity createMyObject(#RequestBody MyObjectEntity myObj) throws MyObjectException
{
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
String email = user.getEmail();
MyObjectEntity myObj = MyObjectService.createMyObject(myObj, email);
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
return myObj;
}
We know a user has logged in from the web-site with a username and password. We know the UI has a token, and they pass it along in the header. Our security uses the SiteMinder example, which means we have a UserDetailsService that goes to a third-party, passes along the token, and we now have the username, password, and the roles the user has. This is normally working well.
We did create a CustomUserDetailsService as follows:
public class CustomUserDetailsService implements UserDetailsService
{
#Override
public UserDetails loadUserByUsername(String accessToken) throws
UsernameNotFoundException,
PreAuthenticatedCredentialsNotFoundException
{
// goto to third-party service to verify token
// get the Custom User and the user roles
// also get some extra data, so a custom user
}
}
So, once we established the token is valid, and we have gotten additional user information from that third-party, and we have the valid role that is authorized for this API ... then we can execute the controller itself. And we see this code is traditional for getting an existing user out of the Spring Security Context.
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
Actually, from what we have read, this is the way to do it when you have a custom user and CustomUserDetails. With this code, we want to get the email of this user. And this all works when we actually test the API with Advanced REST Client. Our QA has to authenticate against the web-site, and they get tokens passed back to the UI, they get those access tokens, and put those in the headers of the Advanced REST Client (or Postman) and this all works.
We even have code to invalidate the security context when the API is over.
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
Against, the real API, with the real progress, this works great.
Now, when it comes to testing, some of the tests work against our secured controllers and some do not. So, here we have a controller to test:
#RequestMapping(value = "/{productId}", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ProductEntity getProductById(#PathVariable("productId") long productId)
{
logger.debug("ProductController: getProductById: productId=" + productId);
CustomUser user = authenticate();
ProductEntity productEntity = service.getById(productId);
logger.debug("ProductController: getProductById: productEntity=" + productEntity);
invalidateUser();
return productEntity;
}
And here is the test:
#Test
public void testMockGetProductByProductId() throws Exception
{
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user("testuser").roles("REGULAR_USER"));
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
This works because even when we get to the controller, we don't need the CustomerUser set, so it works. If the role is the correct role ("REGULAR_USER"), then it works, if the role is not correct, we get a 403 error which are expecting.
But if you look at the Controller I first posted at the top, we NEED the CustomUser to be set, and if it isn't set, then when we try to get that email, we fail. So, we have been looking at multiple ways of setting up a mock user in authentication, so when we get to the Controller we can get that CustomUser already in security context.
I've actually done this before, but that was when we were using the standard spring security user, and not a custom user.
We can definitely establish a CustomUser in the security context, but when it gets to the controller, and this code is run ....
// THIS WORKS
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
// This IF fails because;
// userDetails is of instance User (Spring Security User)
// and not CustomUser.
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
Let me add the code we have for our CustomUser:
public class CustomUser implements UserDetails
{
private static final long serialVersionUID = -6650061185298405641L;
private String userName;
private ArrayList<GrantedAuthority> authorities;
private String firstName;
private String middleName;
private String lastName;
private String email;
private String phone;
private String externalUserId;
// getters/setters
// toString
}
I hope I put enough information here that someone can answer my question. I have spent a day or two scouring the internet for someone who can answer this question to no avail. Some of the answers were a little older from Spring 3 and older Spring Security 3.x. if any more information is needed, please let me know. Thanks!
I wonder ... if I need a CustomUserDetails which implments UserDetails?
Thanks again!
This is probably much easier than what you think.
CustomUser userDetails = new CustomUser();
/* TODO: set username, authorities etc */
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user(userDetails));
This is allowed as long as your CustomUser implements UserDetails interface.
I have custom implementation of AuthenticationTokenProvider abstraction. It has two methods to be overriden that I'm using: CreateAsync, ReceiveAsync.
In OAuthAuthorizationServerOptions I have RefreshTokenProvider set to my custom AuthenticationTokenProvider implementation.
My access tokens expire in 20 minutes. My refresh tokens expire in 24 hours. When access token expires a request comes with grant_type=refresh_token containing refresh token. I observe ReceiveAsync is called. There is a logic of setting Ticket property of AuthenticationTokenReceiveContext. But afterwards CreateAsync method is called, where there is a logic of setting token in AuthenticationTokenCreateContext. The Ticket property of AuthenticationTokenCreateContext does not seem to be that one I have set previously in ReceiveAsync method.
As a result I receive response with new access token and refresh token. I don't want refresh token to be reissued each time I want to exchange my access token, I already have one valid for 24 hours.
Eventually I have found how to answer my question. I can leverage OwinContext.Environment to store a flag which tells that my refresh token is not expired yet so there is no need of creation a new one.
public class RefreshTokenProvider : AuthenticationTokenProvider
{
private const string IsRefreshTokenExpiredName = "IsRefreshTokenExpired";
#region ctor
public RefreshTokenProvider()
{
}
#endregion
public async override Task CreateAsync(AuthenticationTokenCreateContext context)
{
if (!context.OwinContext.Environment.ContainsKey(IsRefreshTokenExpiredName) || (bool)context.OwinContext.Environment[IsRefreshTokenExpiredName])
{
var hours = int.Parse(ConfigurationManager.AppSettings["RefreshTokenExpirationHours"]);
var now = DateTime.UtcNow;
context.Ticket.Properties.IssuedUtc = now;
context.Ticket.Properties.ExpiresUtc = now.AddHours(hours);
context.SetToken(context.SerializeTicket());
}
}
public async override Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { ConfigurationManager.AppSettings["CorsOrigins"] });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Method", new[] { "POST" });
context.DeserializeTicket(context.Token);
if (context.Ticket.Properties.ExpiresUtc > DateTime.UtcNow)
context.OwinContext.Environment[IsRefreshTokenExpiredName] = false;
}
}