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.
Related
We've recently implemented the ability to disable users in our application with an "Active" boolean field in the Identity.AspNetUsers table. Logging in to the back office system (an Angular application) is easily handled with an implicit flow -- simply check the field before calling PasswordSignInAsync.
We can't figure a way to stop a token being issued for any mobile devices using the sister application (written in Flutter) that calls the built in ID Server 4 endpoint /connect/token. Likewise, we can't stop the application from requesting, and then receiving, a valid refresh token. We can't hard delete the user as we have hard links to other tables in the database for auditing purposes.
Any help would be massively appreciated.
We're using DotNET Core 3,1.
EDIT: We're using the password grant type.
When the client asks for a new access token using the refresh token, then the RefreshTokenService is involved. by Customizing refresh token behavior you can lookup if the user is disabled and then reject thew new access token from being issued. See this page for more details about how to do this.
Alternatively you can in the class that implements IPersistedGrantStore add some code to lookup if the user is disabled and then return
return Task.FromResult<PersistedGrant>(null!);
When blocked.
When using the password grant with the built in /connect/token endpoint, you implement the interface ICustomTokenRequestValidator and add it as a Transient to the service collection. This has one method, ValidateAsync, and if the user referenced by your request is valid you simply return and the pipeline continues as normal. If your user is not valid you set the Result.IsError property on CustomTokenRequestValidationContext to true, and supply a string to Result.Error before you return so the token is then not issued.
Inject UserManager<T> and IHttpContextAccessor so you can access the username and user store from the method.
Here's an implementation:
public class CustomTokenRequestValidator : ICustomTokenRequestValidator
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IHttpContextAccessor _httpContextAccessor;
private const string errorMessage = "invalid_username_or_password";
public CustomTokenRequestValidator(
UserManager<ApplicationUser> userManager
, IHttpContextAccessor httpContextAccessor)
{
_userManager = userManager;
_httpContextAccessor = httpContextAccessor;
}
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
{
_httpContextAccessor.HttpContext.Request.Form.TryGetValue("username", out var userOut);
var u = userOut.ToString();
if(u != null)
{
var user = await _userManager.FindByEmailAsync(u);
if(user == null || !user.Active)
{
context.Result.IsError = true;
context.Result.Error = errorMessage;
}
} else
{
context.Result.IsError = true;
context.Result.Error = errorMessage;
}
return;
}
}
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;
}
I have implemented a custom OAuthAuthorizationServerProvider to add a domain constraint for the account login. Everything was good. However, I met a problem that, once the user get the token, they can use it for whatever system they want. For example:
They request the TokenEndpointPath with proper username and password (assume it is the admin account of Tenant 1): http://localhost:40721/api/v1/account/auth and receive the Bearer Token.
Now they use it to access: http://localhost:40720/api/v1/info/admin, which is of Tenant 0. The request is considered Authorized.
I tried changing the CreateProperties method but it did not help:
public static AuthenticationProperties CreateProperties(string userName)
{
var tenant = DependencyUtils.Resolve<IdentityTenant>();
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName },
{ "tenantId", tenant.Tenant.Id.ToString() },
};
return new AuthenticationProperties(data);
}
I also tried overriding ValidateAuthorizeRequest, but it is never called in my debug.
Do I need to implement a check anywhere else, so the Token is only valid for a domain/correct tenant?
(NOTE: a tenant may have multiple domains, so it's great if I can manually perform an account check against correct tenant rather than sticking to a domain. However, it's a plus if I could do that, or else, simply limit the token to the domain is ok)
Not a direct answer to my question (since it's not inside ASP.NET Identity workflow), but the simplest fix I applied was to use ActionFilterAttribute instead.
public class DomainValidationFilter : ActionFilterAttribute
{
public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
// Other Code...
// Validate if the logged in user is from correct tenant
var principal = actionContext.ControllerContext.RequestContext.Principal;
if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)
{
var userId = int.Parse(principal.Identity.GetUserId());
// Validate against the tenant Id of your own storage, and use this code to invalidate the request if it is trying to exploit:
actionContext.Response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, "Invalid Token");
}
return base.OnActionExecutingAsync(actionContext, cancellationToken);
}
}
Then applies the Filter to all actions by registering it in either FilterConfig or WebApiConfig:
config.Filters.Add(new DomainValidationFilter());
The opposite of: How to manually log out a user with spring security?
In my app I have register new user screen, which posts to a controller which creates a new user within db (and does a few obvious checks).I then want this new user to be automatically logged in ... I kind of want somethign like this :
SecurityContextHolder.getContext().setPrincipal(MyNewUser);
Edit
Well I have almost implemented based on the answer to How to programmatically log user in with Spring Security 3.1
Authentication auth = new UsernamePasswordAuthenticationToken(MyNewUser, null);
SecurityContextHolder.getContext().setPrincipal(MyNewUser);
However, when deployed the jsp can not access my MyNewUser.getWhateverMethods() whereas it does when normal login procedure followed. the code that works nomrally, but throws an error when logged in like above is below :
<sec:authentication property="principal.firstname" />
In my controller i have this, which logs user in as normal :
Authentication auth =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
Where user is my custom user object(implementing UserDetails) that is newly created. The getAuthorities() method does this (just because all my users have the same role):
public Collection<GrantedAuthority> getAuthorities() {
//make everyone ROLE_USER
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
GrantedAuthority grantedAuthority = new GrantedAuthority() {
//anonymous inner type
public String getAuthority() {
return "ROLE_USER";
}
};
grantedAuthorities.add(grantedAuthority);
return grantedAuthorities;
}
You can also inject your spring security configured UserDetailsManager to your controller and use that to get the UserDetails which holds the principal and authorities to avoid duplicate code:
// inject
#Autowired
private UserDetailsManager manager;
// use in your method
UserDetails userDetails = manager.loadUserByUsername (token.getUsername ());
Authentication auth = new UsernamePasswordAuthenticationToken (userDetails.getUsername (),userDetails.getPassword (),userDetails.getAuthorities ());
SecurityContextHolder.getContext().setAuthentication(auth);
From the spring security source AbstractAuthenticationProcessingFilter:
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
// you need this
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
Note however that the SecurityContextHolder is usually cleared upon completion of the filter chain.
For anyone trying to do this with Reactive Spring Security, this is what I did and it seemed to work.
private Mono<Authentication> authenticateUser(ServerWebExchange exchange, UserDetails userDetails,String rawPassword)
{
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),rawPassword);
return reactiveAuthenticationManager.authenticate(token).filter(auth -> auth.isAuthenticated()).flatMap(auth ->
{
SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication(auth);
return securityContextRepository.save(exchange,securityContext).then(Mono.just(auth));
});
}
I am having a hard time implementing "Remember Me" functionality in an MVC application with a custom principal. I have boiled it down to ASP.NET not retrieving the authentication cookie for me. I have included a snapshot below from Google Chrome.
Shows the results of Request.Cookies that is set within the controller action and placed in ViewData for the view to read. Notice that it is missing the .ASPXAUTH cookie
Shows the results from the Chrome developer tools. You can see that .ASPXAUTH is included here.
What may be the issue here? Why does ASP.NET not read this value from the cookie collection?
My application uses a custom IPrincipal. BusinessPrincipalBase is a CSLA object that ust implements IPrincipal. Here is the code for that:
[Serializable()]
public class MoralePrincipal : BusinessPrincipalBase
{
private User _user;
public User User
{
get
{
return _user;
}
}
private MoralePrincipal(IIdentity identity) : base(identity)
{
if (identity is User)
{
_user = (User)identity;
}
}
public override bool Equals(object obj)
{
MoralePrincipal principal = obj as MoralePrincipal;
if (principal != null)
{
if (principal.Identity is User && this.Identity is User)
{
return ((User)principal.Identity).Equals(((User)this.Identity));
}
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool Login(string username, string password)
{
User identity = User.Fetch(username, password);
if (identity == null || !identity.IsAuthenticated)
{
identity = (User)User.UnauthenicatedIdentity;
}
MoralePrincipal principal = new MoralePrincipal(identity);
Csla.ApplicationContext.User = principal;
Context.Current.User = identity;
return identity != null && identity.IsAuthenticated;
}
public static void Logout()
{
IIdentity identity = User.UnauthenicatedIdentity;
MoralePrincipal principal = new MoralePrincipal(identity);
ApplicationContext.User = principal;
Context.Current.User = identity as User;
}
public override bool IsInRole(string role)
{
if (Context.Current.User == null || Context.Current.Project == null)
{
return false;
}
string userRole = Context.Current.User.GetRole(Context.Current.Project.Id);
return string.Compare(role, userRole, true) == 0;
}
The application also uses a custom membership provider. Here is the code for that.
public class MoraleMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
bool result = MoralePrincipal.Login(username, password);
HttpContext.Current.Session["CslaPrincipal"] = ApplicationContext.User;
return result;
}
#region Non-Implemented Properties/Methods
public override string ApplicationName
{
get
{
return "Morale";
}
set
{
throw new NotImplementedException();
}
}
// Everything else just throws a NotImplementedException
#endregion
}
I do not think that any of this is related because the bottom line is that the Request.Cookies does not return the authentication cookie. Is it related to the size of the cookie? I heard there are issues to the size of the cookie.
UPDATE: It seems that the issue revolves around subdomains. This site was being hosted with a subdomain and the cookie domain was left blank. Does anyone have any pointers on how I can get the auth cookie to work with all domains (e.g. http://example.com, http://www.example.com, and http://sub.example.com)?
If you are trying to store the actual User object in the cookie itself, it is probably too big to store as a cookie. I am not too familiar with the MVC authentication stuff, but in web forms I generally do the following:
FormsAuthentication.RedirectFromLoginPage(user_unique_id_here, false);
The second parameter is for the persistency you are looking for.
From there I create a custom context (UserContext) that I populate via HttpModule that gives me access to all the user and role information.
Since I do not develop in MVC (yet) or CSLA, I'm not sure how much more help I can be. If I were you, I would also ditch the custom membership provider. You might as well just call MoralePrincipal.Login directly in your Authentication controller.
The rememberMe stuff should be set by the FormsAuthenticationService (in MVC2) or the FormsAuthentication static class in MVC1, if you're using the 'regular' AccountController's code. If you changed that code, did you remember to add in the (optional) boolean param indicating whether to use a persistent cookie or not?
It sounds to me like you're getting a session cookie, but not a persistent cookie.