Programmatically log-in a user using spring security - spring-mvc

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));
});
}

Related

Spring Security form post authentication

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.

Can not use BackOfficeUserPasswordChecker to login Umbraco with external user

I am trying to use BackOfficeUserPasswordChecker to make authentication for Umbraco (with external users).
I customized the OwinStartup:
public class UmbracoCustomOwinStartup
{
public void Configuration(IAppBuilder app)
{
var applicationContext = ApplicationContext.Current;
app.ConfigureUserManagerForUmbracoBackOffice<BackOfficeUserManager, BackOfficeIdentityUser>(
applicationContext,
(options, context) =>
{
var membershipProvider = Umbraco.Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider();
var settingContent = Umbraco.Core.Configuration.UmbracoConfig.For.UmbracoSettings().Content;
var userManager = BackOfficeUserManager.Create(options,
applicationContext.Services.UserService,
applicationContext.Services.EntityService,
applicationContext.Services.ExternalLoginService,
membershipProvider,
settingContent);
// Set your own custom IBackOfficeUserPasswordChecker
userManager.BackOfficeUserPasswordChecker = new CustomPasswordChecker();
return userManager;
});
}
}
And I also defined CustomPasswordChecker which always return ValidCredentials in CheckPasswordAsync method:
public Task<BackOfficeUserPasswordCheckerResult> CheckPasswordAsync(BackOfficeIdentityUser user, string password)
{
return Task.FromResult(BackOfficeUserPasswordCheckerResult.ValidCredentials);
}
When I entered an user name which don't have in Umbraco database, the Umbraco login still throw the message "login failed fo user...". I debugged, and it jumped to CustomPasswordChecker.CheckPasswordAsync() correctly, but the message still throw and I can not log in successfully.
Did I missed something? And how can I make Umbraco accept external users (AD users/ users that is defined in other database,...)?
Is there the user in your Umbraco? If you tried to log in to Umbraco with User that don't have in the Umbraco then it will fall back to the default authentication.
You can try to add the user to your umbraco first.

Mock Custom User in Spring Security Test

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.

How to re-validate token for multi-tenant ASP.NET Identity?

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());

Only allow 1 user login in Spring Sercurity

I have two users: A and B, if user A login first, user B cant login util user A logout. Each user require three login info: storeId, storePassword, userPassword.
If user B same storeId with user A, do not allow login
If user B different storeId with user A, allow login
I use ServletContext to hold users logged, and when logged user click logout, I will remove that user from ServletContext. But I cant hanle when user close brower intead of click logout. I think this is not a good idea
Here is my code
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
//redirectStrategy.sendRedirect(request, response, "/login");
// do whatever you want
ServletContext context = request.getSession().getServletContext();
Object _auths = context.getAttribute("_authentications");
if(_auths != null) {
List<String> auths = (List<String>) _auths;
auths.remove(authentication.getName());
if(auths.size() == 0) {
auths = new ArrayList<String>();
}
context.setAttribute("_authentications", auths);
}
super.onLogoutSuccess(request, response, authentication);
}
does anyone give me a good solution?
Thank you
i have solved my problem, i use SessionRegistry in AuthencationProvide intead of ServletContext
#Autowired
#Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;
List<Object> principals = sessionRegistry.getAllPrincipals();
for (Object principal: principals) {
String[] auths = principal.toString().split(StringPool.DASH);
if(auths.length == 4 && auths[1].equals(storeId)) {
throw new BadCredentialsException(auths[0]+StringPool.DASH+auths[1]);
}
}
this code work well when session time out, user close brower. And i dont need any js source code to handle
You can use the js below to remove user from servletContext on window close:
<script type="text/javascript">
window.onbeforeunload = logout;
function logout(){
// Make an ajax call to remove user from ServletContext
alert("Window close is being called");
return false;
}
</script>

Resources