New at Spring Security here. I was looking at this link 'https://docs.spring.io/spring-security/site/docs/current/guides/html5/form-javaconfig.html#grant-access-to-remaining-resources' and got really stumped at the section Configuring a login view controller`.
When I'm creating a typical form, I usually make the html page that, on click, calls a method in my custom #controller, which sends to my logic, etc.
However, in their example, they state that no controller is needed because everything is 'default'. Can someone explain exactly how their login form can 'connect' to their authentication object? It looks like somehow the credentials can magically pass into the Authentication object despite having no controller method.
Thanks!
There is no controller. When you use the formLogin() method, a UsernamePasswordAuthenticationFilter is registred in the security filter chain and does the authentication job. You can look at the source code here:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
Take again a look into https://docs.spring.io/spring-security/site/docs/current/guides/html5/form-javaconfig.html#configuring-a-login-view-controller. In the code snippet you can actually see, that an internal controller with the request mapping /login is registered. That is why you do not have to implement it on your own. All authentication transfer between view, internal controller and the authentication manager in the background is handled completely transparent to you.
Related
I want to implement this example using Keyclock server with Spring Security 5.
I'm going to use OAuth2.0 authentication with JWT token. I'm interested how I can get the current logged in user into the Rest Endpoint?
I have configured Spring Security not to store user sessions using http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);.
One possible way is to use this code:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
But I don't know is it going to work. Can someone give some advice for that case?
SecurityContextHolder, SecurityContext and Authentication Objects
By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.
SessionManagementConfigurer consist of isStateless() method which return true for stateless policy. Based on that http set the shared object with NullSecurityContextRepository and for request cache NullRequestCache. Hence no value will be available within HttpSessionSecurityContextRepository. So there might not be issue with invalid/wrong details for user with static method
Code:
if (stateless) {
http.setSharedObject(SecurityContextRepository.class,
new NullSecurityContextRepository());
}
if (stateless) {
http.setSharedObject(RequestCache.class, new NullRequestCache());
}
Code:
Method to get user details
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
}
private static String extractPrincipal(Authentication authentication) {
if (authentication == null) {
return null;
} else if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
sessionManagement
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You might like to explore Methods with Spring Security to get current user details with SessionCreationPolicy.STATELESS
After the service validate the token, you can parse it, and put it into the securitycontext, it can contains various data, so you have to look after it what you need. For example, subject contains username etc...
SecurityContextHolder.getContext().setAuthentication(userAuthenticationObject);
The SecurityContextHolder's context maintain a ThreadLocal entry, so you can access it on the same thread as you write it in the question.
Note that if you use reactive (webflux) methodology, then you have to put it into the reactive context instead.
I have few Spring-boot controller classes to expose few rest web-services. Whenever some user tries to access any of those services, I need to invoke an web-service to check whether the user (user id will be passed as RequestHeader) is authorized or not. If not authorised, need to display an error page (freemarker template) to the user.
I don't want to write a method which will invoke the authentication webservice and call that from each controller methods and throw an exception and redirect the user to the access denied error page using #ControllerAdvice as here I have to call the method from all controller methods.
I'm not sure whether I can use WebSecurityConfigurerAdapter/AuthenticationManagerBuilder to call the webservice and do the validation.
I'm looking for some solution where I would write an interceptor and spring-boot will invoke the webservice before calling the controller classes and will be able to redirect to the error page, if validation fails.
As a recommendation, take a few minutes for reading about Spring Security (https://projects.spring.io/spring-security/), you must configure it and probably you will spend more time than expected, anyway you have so much more profits than make security by ourself.
Benefits are things like:
#PreAuthorize("hasRole('ROLE_USER')")
On every place you can get the user logged through the SecurityContext with something like:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
The way SpringSecurity authenticate users is with JWT (JsonWebToken) this is a really nice way because you can pass and retrieve all information you want:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
And you can forget every possible problem (bad authentication, phishing, xss or csrf..) because it works with public/private key and secrets, so anyone can create a token.
in spring security:
i think with tow way logout called: when a session timeout occurred or a user logout itself...
anyway in these ways , destroyedSession called in HttpSessionEventPublisher and SessionRegistry remove SessionInformation from sessionIds list...
when i use below method for force logout specific user , this method just "expired" SessionInformation in SessionRegistry. now when i get all online user "getAllPrincipals()" from SessionRegistry, the user that session expired, is in the list!
#Override
public boolean forceLogOut(int userId){
for (Object username: sessionRegistry.getAllPrincipals()) {
User temp = (User) username;
if(temp.getId().equals(userId)){
for (SessionInformation session : sessionRegistry.getAllSessions(username, false)) {
session.expireNow();
}
}
}
return true;
}
how can i logout 'specific user' or 'sessionId' that session object remove from "Web Server" and "Session Registry" ?
i googling and found HttpSessionContext in Servlet API that can get HttpSession from specific sessionId. and then invalidate session. but i think this method is not completely useful!
(note. this class is deprecated!)
what is the best way? Whether I'm wrong?
Try like this:
#RequestMapping(value="/logout", method = RequestMethod.GET)
public String logoutPage (HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
//new SecurityContextLogoutHandler().logout(request, response, auth);
persistentTokenBasedRememberMeServices.logout(request, response, auth);
SecurityContextHolder.getContext().setAuthentication(null);
}
return "redirect:/login?logout";
}
To logout specific session Id check that link:
how to log a user out programmatically using spring security
I am developing an application in J2E with struts 2 and tomcat v6.
I have a login page in my application where the user will have to type his password by clicking on a virtual keyboard (made on my own).
Before the keyboard appears, i have an action to randomise the characters' . This action also encode all characters for security reasons and set the map of characters and code in session.
The authentication is done with a JDBC realm in tomcat.
What i am trying to do is to decode the user's password. I have tried a filter with the url-pattern "j_security_check" but i found it was not possible to catch this event in filter.
So I am trying to decode the password in the JDBC realm, but it is not working. I have tried to use ServletActionContext.getRequest() in the realm but I am facing a null pointer exception.
Is it possible to get the map stored in session in the realm ?
If it is not, any clues of how to do this are welcome because I haven't found any solution.
One posible solution is writing Custom Authenticator, extending FormAuthenticator
Eg.
//Will expand the basic FORM authentication to include auth based on request headers
public class CustomAuthenticator extends FormAuthenticator{
public boolean authenticate(Request request, Response response, LoginConfig config) throws IOException{
if(request.getUserPrincipal() == null){
Realm realm = context.getRealm();
//Pick the user name and password from the request headers
//you can decode the password here
if(username == null || pass ==null) return super.authenticate(....);
boolean authenticated = realm.authenticate(username, pass);
if(authenticated == false) return false;
//Set the username/password on the session and set the principal in request
session.setNote(Constants.SESS_USERNAME_NOTE, username);
session.setNote(Constants.SESS_PASSWORD_NOTE, password);
request.setUserPrincipal(principal);
register(request, response, principal, Constants.FORM_METHOD, username, pass);
}
return true;
}
}
See also: http://apachecon.com/eu2007/materials/UnderstandingTomcatSecurity.pdf and http://javaevangelist.blogspot.com/2012/12/tomcat-7-custom-valve.html
I have a custom field along with "j_username" and "j_password" on my login.jsp, that I need to authenticate the user. I am using a CustomUsernamePasswordAuthenticationFilter to access the custom field as follows.
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String myCustomField= request.getParameter("myCustomField");
request.getSession().setAttribute("CUSTOM_FIELD", myCustomField);
return super.attemptAuthentication(request, response);
}
}
I tried accessing the session in loadByUsername method of UserDetailsService class but I get an error. Here is the code for my custom UserDetailsService.
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
ServletRequestAttributes attr = (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession();
User userObject = dbObject.retrieveUser(userName,myCustomParameter)
// code here to retrieve my user from the DB using the userName and myCustomParameter that was retrieved from login.jsp and put in the session. Get the custom parameter from the session here.
if (userObject == null)
throw new UsernameNotFoundException("user not found");
return new AuthenticationUserDetails(userObject);
}
Is there any way where I can access this custom parameter for authentication? Sending it through the session doesn't seem to be working.
Wouldn't the session be created AFTER the authentication takes place. So a new authenticated session might be created after your call to attemptAuthentication
Here's the spring doc on the Abstract class you're implementing
http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication%28javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20org.springframework.security.core.Authentication%29
You might be losing the session attribute by the time loadByUsername is called.
I ran into the exact problem.
The problem appeared to be that the RequestAttributes was not bound to the current thread. To make it work, I had to explicitly bind it to the current thread.
In CustomUsernamePasswordAuthenticationFilter, after the statement
request.getSession().setAttribute("CUSTOM_FIELD", myCustomField);
Add:
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
This worked for for me.