After a new user submits a 'New account' form, I want to manually log that user in so they don't have to login on the subsequent page.
The normal form login page going through the spring security interceptor works just fine.
In the new-account-form controller I am creating a UsernamePasswordAuthenticationToken and setting it in the SecurityContext manually:
SecurityContextHolder.getContext().setAuthentication(authentication);
On that same page I later check that the user is logged in with:
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
This returns the authorities I set earlier in the authentication. All is well.
But when this same code is called on the very next page I load, the authentication token is just UserAnonymous.
I'm not clear why it did not keep the authentication I set on the previous request. Any thoughts?
Could it have to do with session ID's not being set up correctly?
Is there something that is possibly overwriting my authentication somehow?
Perhaps I just need another step to save the authentication?
Or is there something I need to do to declare the authentication across the whole session rather than a single request somehow?
Just looking for some thoughts that might help me see what's happening here.
I couldn't find any other full solutions so I thought I would post mine. This may be a bit of a hack, but it resolved the issue to the above problem:
#Autowired
AuthenticationServiceImpl authenticationManager;
public void login(HttpServletRequest request, String userName, String password) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);
// Authenticate the user
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
// Create a new session and add the security context.
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
I had the same problem as you a while back. I can't remember the details but the following code got things working for me. This code is used within a Spring Webflow flow, hence the RequestContext and ExternalContext classes. But the part that is most relevant to you is the doAutoLogin method.
public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
RequestContext requestContext,
ExternalContext externalContext) {
try {
Locale userLocale = requestContext.getExternalContext().getLocale();
this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
return "success";
} catch (EmailAddressNotUniqueException e) {
MessageResolver messageResolvable
= new MessageBuilder().error()
.source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
.code("userRegistration.emailAddress.not.unique")
.build();
requestContext.getMessageContext().addMessage(messageResolvable);
return "error";
}
}
private void doAutoLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(request));
Authentication authentication = this.authenticationProvider.authenticate(token);
logger.debug("Logging in with [{}]", authentication.getPrincipal());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
logger.error("Failure in autoLogin", e);
}
}
Ultimately figured out the root of the problem.
When I create the security context manually no session object is created. Only when the request finishes processing does the Spring Security mechanism realize that the session object is null (when it tries to store the security context to the session after the request has been processed).
At the end of the request Spring Security creates a new session object and session ID. However this new session ID never makes it to the browser because it occurs at the end of the request, after the response to the browser has been made. This causes the new session ID (and hence the Security context containing my manually logged on user) to be lost when the next request contains the previous session ID.
Turn on debug logging to get a better picture of what is going on.
You can tell if the session cookies are being set by using a browser-side debugger to look at the headers returned in HTTP responses. (There are other ways too.)
One possibility is that SpringSecurity is setting secure session cookies, and your next page requested has an "http" URL instead of an "https" URL. (The browser won't send a secure cookie for an "http" URL.)
The new filtering feature in Servlet 2.4 basically alleviates the restriction that filters can only operate in the request flow before and after the actual request processing by the application server. Instead, Servlet 2.4 filters can now interact with the request dispatcher at every dispatch point. This means that when a Web resource forwards a request to another resource (for instance, a servlet forwarding the request to a JSP page in the same application), a filter can be operating before the request is handled by the targeted resource. It also means that should a Web resource include the output or function from other Web resources (for instance, a JSP page including the output from multiple other JSP pages), Servlet 2.4 filters can work before and after each of the included resources. .
To turn on that feature you need:
web.xml
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/<strike>*</strike></url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
RegistrationController
return "forward:/login?j_username=" + registrationModel.getUserEmail()
+ "&j_password=" + registrationModel.getPassword();
I was trying to test an extjs application and after sucessfully setting a testingAuthenticationToken this suddenly stopped working with no obvious cause.
I couldn't get the above answers to work so my solution was to skip out this bit of spring in the test environment. I introduced a seam around spring like this:
public class SpringUserAccessor implements UserAccessor
{
#Override
public User getUser()
{
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
return (User) authentication.getPrincipal();
}
}
User is a custom type here.
I'm then wrapping it in a class which just has an option for the test code to switch spring out.
public class CurrentUserAccessor
{
private static UserAccessor _accessor;
public CurrentUserAccessor()
{
_accessor = new SpringUserAccessor();
}
public User getUser()
{
return _accessor.getUser();
}
public static void UseTestingAccessor(User user)
{
_accessor = new TestUserAccessor(user);
}
}
The test version just looks like this:
public class TestUserAccessor implements UserAccessor
{
private static User _user;
public TestUserAccessor(User user)
{
_user = user;
}
#Override
public User getUser()
{
return _user;
}
}
In the calling code I'm still using a proper user loaded from the database:
User user = (User) _userService.loadUserByUsername(username);
CurrentUserAccessor.UseTestingAccessor(user);
Obviously this wont be suitable if you actually need to use the security but I'm running with a no-security setup for the testing deployment. I thought someone else might run into a similar situation. This is a pattern I've used for mocking out static dependencies before. The other alternative is you can maintain the staticness of the wrapper class but I prefer this one as the dependencies of the code are more explicit since you have to pass CurrentUserAccessor into classes where it is required.
Related
So I have an Angular frontend application, and a Spring backend. Currently, it seems that I have a cookie of JSessionId on my application (which I receive only on login, and not on register, for whatever reason)
(cookies)
I assume it sends those cookies back to the server. (though that's only an assumption)
Now, when I am making a request to the protected server, the only thing I get is this "Please login" popup.
Login popup
When I log in, my UserService logs a user with such details:
UsernamePasswordAuthenticationToken [Principal=User(userId=1, name=Maksym Riabov, username=MRiabov, password={bcrypt}$2a$10$W0XJRQdfxV5XXORkr2bTluIHvFetIVBzmVp51l39T5zLCQk12RV1i, company=null, enabled=true), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ADMIN]]
And what I've noticed is that the sessionId is null there. Why could that be?
To answer some of the questions forward:
Yes, I've pasted {withCredentials: true} to every request. (specific to Angular)
Yes, I've read documentation - I've even tried pasting all the code from it and it seems that it didn't work.
My login controller:
#GetMapping("/login")
public ResponseEntity<String> login() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return ResponseEntity.ok("123123");
}
#PostMapping("/register")
public ResponseEntity<Map<String, String>> register(#RequestBody UserRegisterDto userDto) {
//todo check if name taken
User user = userMapper.toEntity2(userDto);
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setEnabled(true);
//todo remove
Authority authority = authorityRepository.save(new Authority("ADMIN"));
user.setAuthorities(Set.of(authority));
//todo REMOVE!!!!
User savedUser = userRepository.save(user);
System.out.println("registration works!");
return ResponseEntity.ok(Map.of("result",authority.getAuthority().getAuthority()));
}
Now, I am sending a request to the backend (which puts the popup above) like this one:
#PreAuthorize("hasRole('ADMIN')")
#GetMapping("/create")
public ResponseEntity<OnboardingPathDto> createOnboardingPath() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// erased a bit of code here
return ResponseEntity.ok().build();
And as you see I have a method security, which throws the request for auth.
And, the cherry at the top:
#Component
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
http
.csrf().disable().cors().disable()
.authorizeHttpRequests()
.anyRequest().permitAll() //todo this is unsafe
.and().sessionManagement(session -> session.
sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1))//to force only one session per user
//here I tried sessionManagement to do something, but did it do something?
.rememberMe((rememberMe) -> rememberMe.userDetailsService(userDetailsService))
.httpBasic();
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
return new ProviderManager(daoAuthenticationProvider);
}
#Bean
public DaoAuthenticationProvider prov(PasswordEncoder passwordEncoder, UserDetailsService userDetailService) throws Exception {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailService);
return daoAuthenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {//to force only one session per user
return new HttpSessionEventPublisher();
}
I've read through the Spring Security documentation far and wide, and even have taken a course in it, but I still couldn't manage to get it working.
So, what I'm struggling with:
Why can't Spring authenticate through the session even though it is configured to do so? Where is my error?
Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.
Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.
Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.
You are right, this is a bad idea. For sessions in an application running in a browser, only use cookies with those two flag raised (value=true):
secure (exchanged only over https)
http-only (hidden from Javascript).
This means that cookies should not be accessible to the Angular code but automatically set by the browser before sending requests to the backend.
You should also implement CSRF protection (which is the default in spring-security).
Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.
Good idea. This is much better for security, user experience (SSO), and developper experience: most OIDC providers, either on premise (like Keycloak), or in the cloud (like Auth0, Cognito, and many others), already provide with login forms (including Multi Factor Authentication), user registration, profile edition, administration screens (like clients declaration, user roles assignement, etc.). For that:
configure your Spring REST API as a resource-server. I have written tutorials for this there
configure your Angular app either as:
an OAuth2 client. My favorite certified lib for Angular is angular-auth-oidc-client
a BFF client. Backend For Frontend is a pattern where a server-side middleware serves as the only OAuth2 client to hide tokens from the browser. Angular app won't be OAuth2: it will be secured with sessions (haha! your devil is back ;-), the middleware (something like spring-cloud-gateway with tokenRelay filter) will keep this session, associate tokens to it and replace sessions with tokens before forwarding requests to resource-server. Tutorial there.
I'm trying to configure a Spring Boot application with Keycloak to have an endpoint that is both accessible for authenticated and unauthenticated users. For authenticated users, I want to return some extra information. Here is a simple example of what I'm trying to achieve:
#RestController
public class HelloController {
#GetMapping("/")
public String index(Principal principal) {
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal;
if (keycloakPrincipal != null) {
return "Hello " + keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername();
} else {
return "Hello";
}
}
}
application.properties:
keycloak.securityConstraints[0].authRoles[0] = *
keycloak.securityConstraints[0].securityCollections[0].name = Hello
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /*
So far, I only got it to work for one of both cases. If I protect the endpoint using the security constraint above, the endpoint is only accessible to authenticated users. If I remove the security constraint, the endpoint is accessible for everyone, but then the principal will always be null.
Is it possible to achieve the intended behavior?
Have you tried something like Principal principal = SecurityContextHolder.getContext().getAuthentication();?
I believe the Principal as method parameter is only populated on secured endpoints but am unsure if it would exist in the SecurityContext. If not, you need to add a Filter to add it yourself.
I was able to solve the problem by calling the authenticate() method on the HttpServletRequest object. This will trigger the authentication process and will populate the user principal whenever possible. From the docs:
Triggers the same authentication process as would be triggered if the
request is for a resource that is protected by a security constraint.
To avoid triggering an authentication challenge, I pass in a dummy response object to the authenticate() call.
I'm trying to implement JWT based authentication in TomEE 8 (based on Tomcat 9).
I use org.glassfish.soteria:jakarta.security.enterprise:1.0.1 as an implementation of Jakarta Security.
Following this tutorial https://github.com/payara/Payara-Examples/blob/master/javaee/security-jwt-example/src/main/java/fish/payara/examples/security/JWTAuthenticationMechanism.java java class looks like this:
#RequestScoped
public class JWTAuthenticationMechanism implements HttpAuthenticationMechanism
private static final Logger LOGGER = Logger.getLogger(JWTAuthenticationMechanism.class.getName());
/**
* Access to the
* IdentityStore(AuthenticationIdentityStore,AuthorizationIdentityStore) is
* abstracted by the IdentityStoreHandler to allow for multiple identity
* stores to logically act as a single IdentityStore
*/
#Inject
private IdentityStoreHandler identityStoreHandler;
#Inject
private TokenProvider tokenProvider;
#Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
LOGGER.log(Level.INFO, "validateRequest: {0}", request.getRequestURI());
// Get the (caller) name and password from the request
// NOTE: This is for the smallest possible example only. In practice
// putting the password in a request query parameter is highly insecure
String name = request.getParameter("name");
String password = request.getParameter("password");
String token = extractToken(context);
if (name != null && password != null) {
LOGGER.log(Level.INFO, "credentials : {0}, {1}", new String[]{name, password});
// validation of the credential using the identity store
CredentialValidationResult result = identityStoreHandler.validate(new UsernamePasswordCredential(name, password));
if (result.getStatus() == CredentialValidationResult.Status.VALID) {
// Communicate the details of the authenticated user to the container and return SUCCESS.
return createToken(result, context);
}
// if the authentication failed, we return the unauthorized status in the http response
return context.responseUnauthorized();
} else if (token != null) {
// validation of the jwt credential
return validateToken(token, context);
} else if (context.isProtected()) {
// A protected resource is a resource for which a constraint has been defined.
// if there are no credentials and the resource is protected, we response with unauthorized status
return context.responseUnauthorized();
}
// there are no credentials AND the resource is not protected,
// SO Instructs the container to "do nothing"
return context.doNothing();
}
...
User sends login request with username and password, identityStoreHandler validates it. Then we generate JWT token and send it back. Frontend attaches it to each next request.
This works.
validateRequest() is triggered for every request, protected or unprotected. As I understand it comes from the spec and is a desired behaviour.
Now, if token is expired and user sends request to not protected url - it will be rejected, because token is present and invalid.
I want first to check if url is protected or not and only if it's protected check for token presence and validity. But ((HttpMessageContext ) context.isProtected()) always returns false. In controller protected methods are annotated with #RolesAllowed and #PermitAll annotations. I tried this also with web.xml based constraints, but isProtected() is still false.
Why is it always false?
Update
I was under the wrong impression that annotation-based security and via descriptor (web.xml) are interchangeable.
If web.xml doesn't contain any security constraints - requests of unauthenticated user to resources with #RolesAllowed are rejected with 403 error, requests to resources with #PermitAll are fulfilled. This is strange behaviour, both require authenticated user and should be rejected.
If web.xml has auth-constraint tag then context.isProtected() returns true for that url-pattern.
But it still returns false for methods annotated with #RolesAllowed and #PermitAll, if path in those methods doesn't match url-pattern in web.xml.
According to this https://www.ibm.com/support/knowledgecenter/SSEQTP_8.5.5/com.ibm.websphere.base.doc/ae/twbs_jaxrs_impl_securejaxrs_annotations.html
Annotated constraints are additional to any configured security constraints. The JAX-RS runtime environment checks for annotated constraints after the web container runtime environment has checked for security constraints that are configured in the web.xml file.
Does it mean that container based security (Soteria) will not consider annotated methods as protected?
I'm trying to build a two factor authentication flow for shibboleth idp 3. It's set up with the MFA flow with an initial ldap authentication and then my 2FA flow, which is based on the external authn flow.
How can I get user data from the previous ldap flow in my servlet? It seems like request.getAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY) etc. is not set yet. The docs say that LDAP attributes are returned as part of the authentication process and exposed in the LDAPResponseContext. How can I access the context in my servlet?
I also tried to use an attribute-resolver to release a specific value from the AD user profile, but I was not able to find those values in my servlet. Any ideas?
I figured it out, maybe someone else finds it helpful:
The password flow populates the c14n context with the principal name, which is enough for me. To get the principal name in a servlet:
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
try {
String authenticationKey = ExternalAuthentication.startExternalAuthentication(request);
// get userPrincipalName of previous authn
final ProfileRequestContext profileRequestContext = ExternalAuthentication.getProfileRequestContext(authenticationKey, request);
final SubjectCanonicalizationContext c14nContext = profileRequestContext.getSubcontext(SubjectCanonicalizationContext.class);
if (c14nContext != null && c14nContext.getPrincipalName() != null) {
usernameShib = c14nContext.getPrincipalName();
//Subject subjectShib = c14nContext.getSubject();
logger.info(usernameShib);
}
//...
}
I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.