How to handle exceptions properly in custom Spring security 3.0 authentication? - spring

I'm developing a REST service based in tokens. When an user goes to ../rest/authenticate with the user and password via curl, gets a valid token in order to use the whole API.
My problem appears when the user forgets to insert the username, the password or the token in the other methods because i've not managed to handle the Authentication exceptions as I want.
I cand handle the exceptions but tomcat gets the response and inserts some html that I don't expect.
This is the typical response of tomcat.
Is it possible to receive a response like 200 OK which don't have this html code?
At the momment, this is my config:
AuthenticationProcessingFilter
Decides if the url is secured or not. If has to be secured, calls the authentication manager in order to validate it. If receives an authentication exceptions calls the AuthenticationEntryPoint
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private final Collection<String> nonTokenAuthUrls = Lists.newArrayList("/rest","/rest/authenticate");
TokenAuthenticationManager tokenAuthenticationManager;
RestAuthenticationEntryPoint restAuthenticationEntryPoint;
public AuthenticationTokenProcessingFilter(TokenAuthenticationManager tokenAuthenticationManager, RestAuthenticationEntryPoint restAuthenticationEntryPoint) {
this.tokenAuthenticationManager = tokenAuthenticationManager;
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
try{
if(!nonTokenAuthUrls.contains(httpRequest.getRequestURI())){ //Auth by token
String hash = httpRequest.getHeader("token");
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(hash, null);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
SecurityContextHolder.getContext().setAuthentication(tokenAuthenticationManager.authenticate(authentication));
}
response.reset();
chain.doFilter(request, response);
}catch(AuthenticationException authenticationException){
SecurityContextHolder.clearContext();
restAuthenticationEntryPoint.commence(httpRequest, httpResponse, authenticationException);
}
}
AuthenticationManager
public class TokenAuthenticationManager implements AuthenticationManager{
#Autowired
UserService userService;
#Autowired
TokenService tokenService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Object hash = authentication.getPrincipal();
if(hash == null)
throw new BadCredentialsException("Token is required");
User user = tokenService.getUserFromTokenHash((String)hash);
if(user == null)
throw new BadCredentialsException("Non-existent token");
if(!tokenService.validate((String)hash))
throw new BadCredentialsException("Expired Token");
org.springframework.security.core.userdetails.User userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getUserGrantedAuthorities(user.getRoles()));
return new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(), getUserGrantedAuthorities(user.getRoles()));
}
AuthenticationEntryPoint
This class works OK. The code received is 401 unauthorized but the message is in the tomcat html
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.setContentType("application/json");
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage() );
response.getOutputStream().println("{ \"error\": \"" + authenticationException.getMessage() + "\" }");
}
}
The RestAccessDeniedHanler is not called either. It's difficult becasue there are a lot of classes that have to be implemented.
I reviewed some post in stackoverflow and other websites and my approach consist on catching the exceptions in the AuthenticationProcessingFilter and call the AuthenticationEntryPoint manually. I decided to do that becasue I've tried to configure this in the applicationContext-security.xml with no success.
appliacionContext-security.xml
<b:bean id="restAuthenticationEntryPoint" class="...web.security.RestAuthenticationEntryPoint" />
<b:bean id="tokenAuthenticationManager" class="...dp.web.security.TokenAuthenticationManager"/>
<b:bean id="AuthenticationTokenProcessingFilter" class="...web.security.AuthenticationTokenProcessingFilter">
<b:constructor-arg type="...dp.web.security.TokenAuthenticationManager" ref="tokenAuthenticationManager"></b:constructor-arg>
<b:constructor-arg type="...dp.web.security.RestAuthenticationEntryPoint" ref="restAuthenticationEntryPoint"></b:constructor-arg>
</b:bean>
<b:bean id="accessDeniedHandler" class="...dp.web.security.RestAccessDeniedHandler">
</b:bean>
<http realm="Protected REST API" pattern="/rest/**" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="restAuthenticationEntryPoint">
<custom-filter ref="AuthenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
<access-denied-handler ref="accessDeniedHandler"/>
</http>
how can I send a clean response with the error code and a message?

You can use error-pages in your web.xml to intercept Tomcat's error page. For example,
<error-page>
<error-code>404</error-code>
<location>/404</location>
</error-page>
Now you use RequestMapping to map /404 to a page that returns your JSON response without any HTML:
#RequestMapping(value = "/404", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
#ResponseBody
public ResponseEntity<ResponseStatus> handle404() {
HttpStatus status = null;
ResponseStatus responseStatus = new ResponseStatus("404", "Wrong path to resource.");
status = HttpStatus.NOT_FOUND;
ResponseEntity<ResponseStatus> response = new ResponseEntity<ResponseStatus>(responseStatus, status);
return response;
}
Which will simply return a JSON object called Response Status that contains an error code and error message as fields.

Related

How to track all login logs about the jwt in spring security

Recently, I started to make a platform and chose spring security as the back end and angular as the front end. And I want to track all login logs, such as failed login, successful login, username does not exist, incorrect password, etc.
I try to use spring aop to track all login logs, but I only get the logs when the login is successful.
These are the jwt filter and the spring aop code.
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/* get username and password in user request by jwt */
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException();
}
}
/* create jwt token when user pass the attemptAuthentication */
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
String key = "securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";
String token = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(2)))
.signWith(Keys.hmacShaKeyFor(key.getBytes()))
.compact();
response.addHeader("Authorization", "Bearer " + token);
}
}
#Aspect
#Component
public class LoginLogAOP {
private static final Logger logger = LoggerFactory.getLogger(LoginLogAOP.class);
#AfterReturning(pointcut="execution(* org.springframework.security.authentication.AuthenticationManager.authenticate(..))"
,returning="result")
public void afteReturn(JoinPoint joinPoint,Object result) throws Throwable {
logger.info("proceed: {}", joinPoint.getArgs()[0]);
logger.info("result: {}", ((Authentication) result));
logger.info("user: " + ((Authentication) result).getName());
}
}
Has anyone tracked login logs through Spring Security jwt? Thank you very much for your help!
Your advice type #AfterReturning does exactly what the name implies: It kicks in after the method returned normally, i.e. without exception. There is another advice type #AfterThrowing, if you want to intercept a method which exits by throwing an exception. Then there is the general #After advice type which kicks in for both. It is like the supertype for the first two subtypes.
And then of course if you want to do more than just react to method results and log something, but need to actually modify method parameters or method results, maybe handle exceptions (which you cannot do in an "after" advice), you can use the more versatile, but also more complex #Around advice.
Actually, all of what I said is nicely documented in the Spring manual.
Update: I forgot to mention why #AfterReturning does not capture the cases you are missing in your log: Because according to the documentation for AuthenticationManager.authenticate(..), in case of disabled or locked accounts or wrong credentials the method must throw certain exceptions and not exit normally.

How to allow original HttpServletRequest to handle login using Spring Boot Security

I am using Spring Security and I would like to handle authentication by the jee container.
So the only reason for spring security is authorization. All the pre authenticated filters are configured already and working.
I created a custom endpoint to trigger authentication.
import javax.http.HttpServletRequest;
...
#PostMapping("/auth/login")
public void login(HttpServletRequest request) throws ServletException {
request.login("something", "something");
}
...
It seems that HttpServletRequest is wrapped by SecurityContextHolderAwareRequestWrapper. The following method (which is provided by spring boot) handles authentication:
HttpServlet3RequestFactory$Servlet3SecurityContextHolderAwareRequestWrapper.login(...)
#Override
public void login(String username, String password) throws ServletException {
if (isAuthenticated()) {
throw new ServletException("Cannot perform login for '" + username
+ "' already authenticated as '" + getRemoteUser() + "'");
}
AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;
if (authManager == null) {
HttpServlet3RequestFactory.this.logger.debug(
"authenticationManager is null, so allowing original HttpServletRequest to handle login");
super.login(username, password);
return;
}
Authentication authentication;
try {
authentication = authManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
}
catch (AuthenticationException loginFailed) {
SecurityContextHolder.clearContext();
throw new ServletException(loginFailed.getMessage(), loginFailed);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
If authManager is null it should trigger login from the original HttpServletRequest.
But that is the thing it isn't null because of the AnonymousAuthenticationProvider and PreAuthenticatedAuthenticationProvider attached to the ProviderManager. Is there another way to trigger login on its original HttpServletRequest?
I got around it by using my container servlet request in my method:
import com.ibm.ws.webcontainer.srt.SRTServletRequest;
#PostMapping("/auth/login")
public void login(SRTServletRequest request) throws ServletException {
request.login("something", "something");
}

spring-security block websocket (sockjs)

In one of my projects I configured both rest services and websockets, both go through spring security filter that check for JWT. For websockets on the client side, application uses sockjs & stomp (on Angular2) and Spring websockets on the server side (Tomcat 8). When I open connection with Spring security enabled then I get below error two seconds after it gets opened. However when I open connection without spring security enabled connection does not get dropped.
angular2 connect()/subscribe()/send() - all go with JWT token
public connect() : void {
let sockjs = new SockJS('/rest/add?jwt=' + this.authService.getToken());
let headers : any = this.authService.getAuthHeader();
this.stompClient = Stomp.over(sockjs);
this.stompClient.connect(this.token, (frame) => {
this.log.d("frame", "My Frame: " + frame);
this.log.d("connected()", "connected to /add");
this.stompClient.subscribe('/topic/addMessage', this.authService.getAuthHeader(), (stompResponse) => {
// this.stompSubject.next(JSON.parse(stompResponse.body));
this.log.d("result of WS call: ", JSON.parse(stompResponse.body).message);
}, (error) => {
this.log.d(error);
});
});
}
public send(payload: string) {
this.stompClient.send("/app/add", this.token, JSON.stringify({'message': payload}));
}
JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public JwtAuthenticationFilter() {
super("/rest/**");
}
#Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return true;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String token = null;
String param = request.getParameter("jwt");
if(param == null) {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
throw new JwtAuthenticationException("No JWT token found in request headers");
}
token = header.substring(7);
} else {
token = param;
}
JwtAuthenticationToken authRequest = new JwtAuthenticationToken(token);
return getAuthenticationManager().authenticate(authRequest);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
// As this authentication is in HTTP header, after success we need to continue the request normally
// and return the response as if the resource was not secured at all
chain.doFilter(request, response);
}
}
JwtAuthenticationProvider.java
#Service
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Autowired
private SecurityService securityService;
#Override
public boolean supports(Class<?> authentication) {
return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
}
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
#Override
#Transactional(readOnly=true)
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
String token = jwtAuthenticationToken.getToken();
User user = securityService.parseToken(token);
if (user == null) {
throw new JwtAuthenticationException("JWT token is not valid");
}
return new AuthenticatedUser(user);
}
}
JwtAuthenticationSuccessHandler.java
#Service
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// We do not need to do anything extra on REST authentication success, because there is no page to redirect to
}
}
RestAuthenticationEntryPoint.java
#Service
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
// This is invoked when user tries to access a secured REST resource without supplying any credentials
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Weboscket configuration:
<websocket:message-broker
application-destination-prefix="/app">
<websocket:stomp-endpoint path="/add">
<websocket:sockjs />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic, /queue" />
</websocket:message-broker>
and my spring security
<context:component-scan base-package="com.myapp.ws.security"/>
<sec:global-method-security pre-post-annotations="enabled" />
<!-- everyone can try to login -->
<sec:http pattern="/rest/login/" security="none" />
<!--<sec:http pattern="/rest/add/**" security="none" />-->
<!-- only users with valid JWT can access protected resources -->
<sec:http pattern="/rest/**" entry-point-ref="restAuthenticationEntryPoint" create-session="stateless">
<!-- JWT is used to disabled-->
<sec:csrf disabled="true" />
<!-- don't redirect to UI login form -->
<sec:custom-filter before="FORM_LOGIN_FILTER" ref="jwtAuthenticationFilter" />
</sec:http>
<bean id="jwtAuthenticationFilter" class="com.myapp.ws.security.JwtAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="jwtAuthenticationSuccessHandler" />
</bean>
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="jwtAuthenticationProvider" />
</sec:authentication-manager>
Your problem doesn't related to security. You just pass wrong arguments in Stomp connect and subscribe functions.
The connect() method also accepts two other variants if you need to
pass additional headers:
client.connect(headers, connectCallback);
client.connect(headers, connectCallback, errorCallback);
where header is a map and connectCallback and errorCallback are
functions.
this.stompClient.connect(this.token, (frame) => {
should be
this.stompClient.connect({}, (frame) => {
and
You can use the subscribe() method to subscribe to a destination. The
method takes 2 mandatory arguments: destination, a String
corresponding to the destination and callback, a function with one
message argument and an optional argument headers, a JavaScript object
for additional headers.
var subscription = client.subscribe("/queue/test", callback);
this.stompClient.subscribe('/topic/addMessage', this.authService.getAuthHeader(), (stompResponse) => {
should be
this.stompClient.subscribe('/topic/addMessage', (stompResponse) => {
Documentation http://jmesnil.net/stomp-websocket/doc/
#user1516873 finally I got it working:
passing correct parameters to STOMP fixed one problem
adding {transports: ["websocket"]} was not necessary (it works without it)
Problem was that I was using angular-cli server on port 4200 with proxy file like this:
{
"/rest": {
"target": "http://localhost:8080",
"secure": false
}
}
but should have been like this:
{
"/rest": {
"target": "http://localhost:8080",
"secure": false,
"ws": true,
"logLevel": "debug"
}
}
so through all the combinations of configuration I was always checking through 4200 proxy server and very rarely through native 8080 directly. I just didn't know that angular-cli proxy does not support when spring security is applied. I will accept your answer as you helped a lot!

Spring Security login with a rest web service

I have a SpringMVC web application that needs to authenticate to a RESTful web service using Spring Security.And i need to access this same application through a rest client.
Here is What I need to implement
The accept header is application/json(For a java rest client )
After a successful login, It will be sent a token(Or sessionId) to rest client in the format of json
After a login failure,It will be sent error message in the format of json.
For a web request
After a successful login,It will be redirecting to a success jsp page.
After a login failure,It will be sent error message to the same loin page.
How can i do this with spring mvc and spring security?.I have very less time to do this,any one please give me an example with spring-security.xml.
Thanks
my recommendation is as below. you can use standard web security to call RESTFul service, first authenticate with user and password and get cookies, if using java based server, send this as cookie to server on subsequent rest calls. I have written is Spring Java code which can get session cookies for you.
There is no need for separate json service to get token.
public class RestAuthClient {
String baseUrl = "http://localhost:8888/ecom";
public String authenticateGetCookie(String user, String password){
HttpMessageConverter<MultiValueMap<String, ?>> formHttpMessageConverter = new FormHttpMessageConverter();
HttpMessageConverter<String> stringHttpMessageConverternew = new StringHttpMessageConverter();
List<HttpMessageConverter<?>> messageConverters = new LinkedList<HttpMessageConverter<?>>();
messageConverters.add(formHttpMessageConverter);
messageConverters.add(stringHttpMessageConverternew);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("j_username", user);
map.add("j_password", password);
String authURL = baseUrl+"/j_spring_security_check";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map,
requestHeaders);
ResponseEntity<String> result = restTemplate.exchange(authURL, HttpMethod.POST, entity, String.class);
HttpHeaders respHeaders = result.getHeaders();
System.out.println(respHeaders.toString());
System.out.println(result.getStatusCode());
String cookies = respHeaders.getFirst("Set-Cookie");
return cookies;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}
Consider implementing your custom AuthenticationSuccessHandler and AuthenticationFailureHandler as described below.
You might also need to implement some simple controllers which you will be redirecting to from AuthenticationHandlers. There's a good explanation of how to implement REST auth in Spring. So I beleive combining these two answers will give you a solution.
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// get accept headers from request
// Redirect successfully logged in user to another url depending on the accept headers)
// put session id in response if needed
((WebAuthenticationDetails)SecurityContextHolder.getContext().getAuthentication().getDetails()).getSessionId();
String targetUrl = ""; //TODO insert here
response.sendRedirect(targetUrl);
}
}
public class AuthenticationFailureHandlerImpl extends SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// get accept headers from request
// set failure url
// Do redirecting job
setDefaultFailureUrl(FAILURE_URL);
super.onAuthenticationFailure(request, response, exception);
}
}
In your security.xml
<http entry-point-ref="loginUrlAuthenticationEntryPoint" access-denied-page="/WEB-INF/views/errors/error403.jsp" access-decision-manager-ref="accessDecisionManager">
...
<custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/>
...
</http>
<!-- Login filter and entry point -->
<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/signin" /></beans:bean>
<beans:bean id="loginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="filterProcessesUrl" value="/j_spring_security_check"/>
<beans:property name="authenticationSuccessHandler" ref="authSuccessHandler"/>
<beans:property name="authenticationFailureHandler" ref="authFailureHandler"/></beans:bean>
<!-- Login filter and entry point -->
<beans:bean id="authSuccessHandler" class="com.example.security.AuthenticationSuccessHandlerImpl"/>
<beans:bean id="authFailureHandler" class="com.example.security.AuthenticationFailureHandlerImpl"/>
</beans:beans>

Auto login after successful registration

hey all
i want to make an auto login after successful registration in spring
meaning:
i have a protected page which requires login to access them
and i want after registration to skip the login page and make an auto login so the user can see that protected page, got me ?
i am using spring 3.0 , spring security 3.0.2
how to do so ?
This can be done with spring security in the following manner(semi-psuedocode):
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
#Controller
public class SignupController
{
#Autowired
RequestCache requestCache;
#Autowired
protected AuthenticationManager authenticationManager;
#RequestMapping(value = "/account/signup/", method = RequestMethod.POST)
public String createNewUser(#ModelAttribute("user") User user, BindingResult result, HttpServletRequest request, HttpServletResponse response) {
//After successfully Creating user
authenticateUserAndSetSession(user, request);
return "redirect:/home/";
}
private void authenticateUserAndSetSession(User user, HttpServletRequest request) {
String username = user.getUsername();
String password = user.getPassword();
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
// generate session if one doesn't exist
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
}
}
Update: to only contain how to create the session after the registration
In Servlet 3+ you can simply do request.login("username","password") and if successful, redirect to whatever page you want. You can do the same for auto logout.
Here is the link to the section of the documentation that talks about this: http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#servletapi-3
Just a comment to the first reply on how to autowire authenticationManager.
You need to set an alias when you declare authentication-manager in either your applicantion-servlet.xml or applicationContext-security.xml file:
<authentication-manager alias="authenticationManager>
<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
Also, when you authenticate, it may throw an AuthenticationException, so you need to catch it:
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
try{
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch(Exception e){
e.printStackTrace();
}
return "redirect:xxxx.htm";
Configure web.xml to allow Spring Security to handle forwards for a login processing url.
Handle registration request, e.g. create user, update ACL, etc.
Forward it with username and password to login processing url for authentication.
Gain benefits of entire Spring Security filter chain, e.g. session fixation protection.
Since forwards are internal, it will appear to the user as if they are registered and logged in during the same request.
If your registration form does not contain the correct username and password parameter names, forward a modified version of the request (using HttpServletRequestWrapper) to the Spring Security login endpoint.
In order for this to work, you'll have to modify your web.xml to have the Spring Security filter chain handle forwards for the login-processing-url. For example:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- Handle authentication for normal requests. -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Handle authentication via forwarding for internal/automatic authentication. -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/login/auth</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Source: mohchi blog
I incorporated the same scenario, below is the code snippet. To get the instance of AuthenticationManager, you will need to override the authenticationManagerBean() method of WebSecurityConfigurerAdapter class
SecurityConfiguration(extends WebSecurityConfigurerAdapter)
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Controller
#Autowired
protected AuthenticationManager authenticationManager;
#PostMapping("/register")
public ModelAndView registerNewUser(#Valid User user,BindingResult bindingResult,HttpServletRequest request,HttpServletResponse response) {
ModelAndView modelAndView = new ModelAndView();
User userObj = userService.findUserByEmail(user.getEmail());
if(userObj != null){
bindingResult.rejectValue("email", "error.user", "This email id is already registered.");
}
if(bindingResult.hasErrors()){
modelAndView.setViewName("register");
return modelAndView;
}else{
String unEncodedPwd = user.getPassword();
userService.saveUser(user);
modelAndView.setViewName("view_name");
authWithAuthManager(request,user.getEmail(),unEncodedPwd);
}
return modelAndView;
}
public void authWithAuthManager(HttpServletRequest request, String email, String password) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, password);
authToken.setDetails(new WebAuthenticationDetails(request));
Authentication authentication = authenticationManager.authenticate(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Using SecurityContextHolder.getContext().setAuthentication(Authentication) gets the job done but it will bypass the spring security filter chain which will open a security risk.
For e.g. lets say in my case when user reset the password, I wanted him to take to the dashboard without login again. When I used the above said approach, it takes me to dashboard but it bypassed my concurrency filter which I have applied in order to avoid concurrent login. Here is the piece of code which does the job:
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(empId, password);
Authentication auth = authenticationManager.authenticate(authToken);
SecurityContextHolder.getContext().setAuthentication(auth);
Use login-processing-url attribute along with a simple change in web.xml
security-xml
<form-login login-page="/login"
always-use-default-target="false"
default-target-url="/target-url"
authentication-failure-url="/login?error"
login-processing-url="/submitLogin"/>
web.xml
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/submitLogin</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
By adding this piece of code in web.xml actually does the job of forwarding your explicit forward request which you will make during auto login and passing it to the chain of spring security filters.
Hope it helps
This is an alternative to the Servlet 3+ integration. If you're using Spring Security's form login, then you can simply delegate to your login page. For example:
#PostMapping("/signup")
public String signUp(User user) {
// encode the password and save the user
return "forward:/login";
}
Assuming you have username and password fields in your form, then the 'forward' will send those parameters and Spring Security will use those to authenticate.
The benefit I found with this approach is that you don't duplicate your formLogin's defaultSuccessUrl (example security setup below). It also cleans up your controller by not requiring a HttpServletRequest parameter.
#Override
public void configure(HttpSecurity http) {
http.authorizeRequests()
.antMatchers("/", "/signup").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll();
}
Spring Monkey's answer works great but I encountered a tricky problem when implementing it.
My problem was because I set the registration page to have "no security", eg:
<http pattern="/register/**" security="none"/>
I think this causes no SecurityContext initialized, and hence after user registers, the in-server authentication cannot be saved.
I had to change the register page bypass by setting it into IS_AUTHENTICATED_ANONYMOUSLY
<http authentication-manager-ref="authMgr">
<intercept-url pattern="/register/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
...
</http>
This is answer to above question
In Controller:
#RequestMapping(value = "/registerHere", method = RequestMethod.POST)
public ModelAndView registerUser(#ModelAttribute("user") Users user, BindingResult result,
HttpServletRequest request, HttpServletResponse response) {
System.out.println("register 3");
ModelAndView mv = new ModelAndView("/home");
mv.addObject("homePagee", "true");
String uname = user.getUsername();
if (userDAO.getUserByName(uname) == null) {
String passwordFromForm = user.getPassword();
userDAO.saveOrUpdate(user);
try {
authenticateUserAndSetSession(user, passwordFromForm, request);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("register 4");
log.debug("Ending of the method registerUser");
return mv;
}
Further above method in controller is defined as:
`private void authenticateUserAndSetSession(Users user, String passwor`dFromForm, HttpServletRequest request){
String username = user.getUsername();
System.out.println("username: " + username + " password: " + passwordFromForm);
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, passwordFromForm, userDetails.getAuthorities());
request.getSession();
System.out.println("Line Authentication 1");
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetails(request));
System.out.println("Line Authentication 2");
Authentication authenticatedUser = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
System.out.println("Line Authentication 3");
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
System.out.println("Line Authentication 4");
}
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());// creates context for that session.
System.out.println("Line Authentication 5");
session.setAttribute("username", user.getUsername());
System.out.println("Line Authentication 6");
session.setAttribute("authorities", usernamePasswordAuthenticationToken.getAuthorities());
System.out.println("username: " + user.getUsername() + "password: " + user.getPassword()+"authorities: "+ usernamePasswordAuthenticationToken.getAuthorities());
user = userDAO.validate(user.getUsername(), user.getPassword());
log.debug("You are successfully register");
}
Other answers didnt suggest to put it in try/catch so one does not realize why logic is not working as code runs...and nothing is there neither error or exception on console. So if you wont put it in try catch you wont get exception of bad credentials.
I'm not sure if you are asking for this, but in your Spring Security configuration you can add a "remember-me" tag. This will manage a cookie in your client, so next time (if the cookie hasn't expired) you'll be logged automatically.
<http>
...
<remember-me />
</http>

Resources