Spring Security Custom Logout Filter - spring-security

I created a custom security filter that handles LOGOUT_FILTER. Debugging my app, it does reach to #Override public void onLogoutSuccess.
I need to be redirected to a different domain/context/url outside my app. However, response.sendRedirect doesnt redirect at all to that url.
In Spring Security 3.1 book from RWinch, apparently you can do this, here's the snippet:
<http ...>
...
<logout logout-url="/logout"
logout-success-url="https://${cas.server.host}/cas/logout"/>
</http>
CAS Server is in another context/domain which is different from context app.

You can use LogoutSuccessHandler :
pHttp.logout()
.logoutSuccessHandler(new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest pRequest,
HttpServletResponse pResponse,
Authentication pAuthentication) throws IOException,
ServletException {
pResponse.sendRedirect("https://${cas.server.host}/cas/logout");
}
});
I let you translate it in XML config ;)

Related

How to obtain HttpSession from within the loadUserByUsername method?

My web app is built with Spring MVC (4.2.9.RELEASE) and Spring Security (3.2.5.RELEASE). I use the loadUserByUsername method to find the user from the database.
public class MyUserDetailsServiceImpl implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
//how to get the HttpSession from within this methd?
}
....
}
How can I get the HttpSession from within this method?
In the applicationContext.xml file, I have the following for security:
<http create-session="always" use-expressions="true" request-matcher="regex" entry-point-ref="authenticationEntryPoint" >
....
</http>
There is a way to get current request:
Get the request object from RequestHolderContext and from that one get session:
public Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(requestAttributes -> ServletRequestAttributes.class.isAssignableFrom(requestAttributes.getClass()))
.map(requestAttributes -> ((ServletRequestAttributes) requestAttributes))
.map(ServletRequestAttributes::getRequest);
}
Use it like this:
Optional<HttpServletRequest> request = getCurrentHttpRequest();
HttpServletRequest currentRequest = request.get();
Hope this helps you
There is an answer exactly to your question at Spring Security documentation of more recent version (5.1.5 for instance). In short:
How do I access the HttpSession from a UserDetailsService?
You can’t, since the UserDetailsService has no awareness of the servlet API.
The #Atul's answer can be a good workaround but please be aware that RequestContextHolder internally relies on the current thread and thus may miss the session if called from another thread.
Before making my post, I already have a solution and it seems working, but not sure whether my solution is error-free or Spring already has a more elegant solution. Here is my solution:
I built filter as follows:
public class MyFilter implements Filter {
...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain
chain) throws IOException, ServletException {
//create a thread-safe object and record the request object
}
}
Then, in the loadUserByUsername method, get the request object (thus the HTTP session) from the thread-safe object.
Comments? Please feel free let me know whether this approach has any problem.
UPDATE
MyFilter is declared in web.xml, together with Spring Security's DelegatingFilterProxy.

Spring OAuth2 - There is no client authentication. Try adding an appropriate authentication filter

We have an application which is using spring-security-oauth2:1.0. I was trying to change it to a newer version, spring-security-oauth2:2.0.7.RELEASE. Some classes were removed, some package structure is changed, I managed to sort out all those things and I was able to start the server without any issue. But I am facing a strange issue here.
With OAuth2 - 1.0 version, when the user logs in we used to do a GET request on /oauth/token, For example :
http://localhost:8080/echo/oauth/token?grant_type=password&client_id=ws&client_secret=secret&scope=read,write&username=john#abc.com&password=password123
and It used to work just fine.
When I try the same thing, First of all I am not able to make a GET request because of the logic in TokenEndPoint.java
private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));
#RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
I have tried to make a POST request same as above URL, but I get InsufficientAuthenticationException with the error message
There is no client authentication. Try adding an appropriate authentication filter
This is because of the following POST request controller in TokenEndpoint.java. When I debug, I see that principal is null.
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
//principal is null here
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
.............
}
I have an authentication filter and it worked well when I used version 1.0. This is the relevant prats of my config:
<authentication-manager xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
<bean id="userDetailsService" class="com.hcl.nc.service.UserDetailsService">
<constructor-arg><ref bean="sessionFactory" /></constructor-arg>
</bean>
I always thought that the request will be authenticated by authentication-provider and goes to token-endpoint but that does not seem to be the correct flow. After debugging the application with version 2.0.7, now I really doubt my understanding about the flow.
Could somebody please explain why it worked in previous version and why it's not working now?
Do I have do to something different to get a OAuth token??
NOTE: I have already checked these questions : here, here, here. But I was not able to find the correct solution.
I don't know the previous version, but I know a bit about 2.0.7.
I suspect your problem is that your TokenEndpoint security tries to authenticate your clients against your user service.
The TokenEndpoint is protected by a BasicAuthenticationFilter. By default this filter would use an AuthenticationManager instance, which itself holds an AuthenticationProvider, which itself depends on an instance of UserDetailsService.
The trick is that this particular instance of UserDetailsService must be client based, not user based : that's why there is a ClientDetailsUserDetailsService, which adapts ClientDetailsService to UserDetailsService.
Normally all this stuff is already done by default when you use the framework's configuration classes AuthorizationServerConfigurerAdapter, #EnableAuthorizationServer, etc..
I had the same problem and my application.yml had this line:
servlet:
path: /auth
so the token address was: /auth/oauth/token
I remove the path from application.yml so the token path became:
/oauth/token
And everything works fine.
I hope this help
One of the problems of the following error, can be that authentication was not performed. I have encountered this problem with older implementation of Spring.
verify that:
TokenEndpoint -> postAccessToken method. Check if Principal is not null. If it is null it means that Basic Authroziation was not performed.
One of the solution to add filter was to use:
#Configuration
public class FilterChainInitializer extends AbstractSecurityWebApplicationInitializer {
}
More information about AbstractSecurityWebApplicationInitializer can be found in Spring docs
The problem can be because of opening all requests. You should remove it.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/**");
}
in my case, i found this config:
security.allowFormAuthenticationForClients(); // here
then post this
http://localhost:8081/sso/oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=Yk4Sum&redirect_uri=http://localhost:8082/sso-demo/passport/login
its works for me, try it
#Configuration
#EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(Oauth2Config.class);
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients(); // here
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { // #formatter:off
clients.inMemory()
.withClient("unity-client")
.secret("unity")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
;
} // #formatter:on
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}
I am following this tutorial - Practical Guide to Building an API Back End with Spring Boot'. See https://www.infoq.com/minibooks/spring-boot-building-api-backend , But with the latest SpringBoot Version(2.7)
and I run into this problem:
org.springframework.security.authentication.InsufficientAuthenticationException: There is no client authentication. Try adding an appropriate authentication filter. at org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(TokenEndpoint.java:91) ~[spring-security-oauth2-2.3.5.RELEASE.jar:na]
My solution/fix was to annotate WebSecurityGlobalConfig with #EnableWebSecurity because in the original course this annotation was missing.
So adding this annotaiton has fixed the error for me.

Spring Boot + OAuth2 + Google Login - How to implement logout

I have a Auth Server implementation with Spring Boot + OAuth2 + Login through Google. And a resource server for my backend data services. I have used JDBC token store. Everything works great. But I am having difficulty in understanding logout implementation. Currently, whenever user clicks on logout I just delete the token from the browser local storage but the session stays active in the Auth server hence I don't need to login again. What I want is whenever use clicks on logout I want to invalidate the session and force him to login again.
Is there any nice way to do this? I currently don't have any logout configuration in my Spring Boot Auth server configuration.
Thanks
Try registering a LogoutSuccessHandler to do that. Something along the lines of:
#Configuration
#EnableWebSecurity
#EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Bean
public DefaultTokenServices tokenServices() {
return new DefaultTokenServices();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("myResourceId");
resources.tokenServices(tokenServices());
}
#Override
public void configure(HttpSecurity http) throws Exception {
// configure http security here...
http.logout().logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
OAuth2AccessToken token = tokenServices().getAccessToken((OAuth2Authentication) authentication);
tokenServices().revokeToken(token.getValue());
}
});
}
}

Spring Security OAuth2 (google) web app in redirect loop

I am trying to build a Spring MVC application and securing it with Spring Security OAuth2 and the provider is Google. I was able to get the web app working without security and with form login. However, I am not able to get OAuth with google to work. Google app setup is fine as I can get the call backs etc to work with a non Spring Security app.
My security config is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:sec="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<sec:http use-expressions="true" entry-point-ref="clientAuthenticationEntryPoint">
<sec:http-basic/>
<sec:logout/>
<sec:anonymous enabled="false"/>
<sec:intercept-url pattern="/**" access="isFullyAuthenticated()"/>
<sec:custom-filter ref="oauth2ClientContextFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
<sec:custom-filter ref="googleAuthenticationFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</sec:http>
<b:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
<sec:authentication-manager alias="alternateAuthenticationManager">
<sec:authentication-provider>
<sec:user-service>
<sec:user name="user" password="password" authorities="DOMAIN_USER"/>
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</b:beans>
The OAuth2 protected resource is as follows:
#Configuration
#EnableOAuth2Client
class ResourceConfiguration {
#Autowired
private Environment env;
#Resource
#Qualifier("accessTokenRequest")
private AccessTokenRequest accessTokenRequest;
#Bean
public OAuth2ProtectedResourceDetails googleResource() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("google-app");
details.setClientId(env.getProperty("google.client.id"));
details.setClientSecret(env.getProperty("google.client.secret"));
details.setAccessTokenUri(env.getProperty("google.accessTokenUri"));
details.setUserAuthorizationUri(env.getProperty("google.userAuthorizationUri"));
details.setTokenName(env.getProperty("google.authorization.code"));
String commaSeparatedScopes = env.getProperty("google.auth.scope");
details.setScope(parseScopes(commaSeparatedScopes));
details.setPreEstablishedRedirectUri(env.getProperty("google.preestablished.redirect.url"));
details.setUseCurrentUri(false);
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
private List<String> parseScopes(String commaSeparatedScopes) {
List<String> scopes = newArrayList();
Collections.addAll(scopes, commaSeparatedScopes.split(","));
return scopes;
}
#Bean
public OAuth2RestTemplate googleRestTemplate() {
return new OAuth2RestTemplate(googleResource(), new DefaultOAuth2ClientContext(accessTokenRequest));
}
#Bean
public AbstractAuthenticationProcessingFilter googleAuthenticationFilter() {
return new GoogleOAuthentication2Filter(new GoogleAppsDomainAuthenticationManager(), googleRestTemplate(), "https://accounts.google.com/o/oauth2/auth", "http://localhost:9000");
}
}
The custom authentication filter which I have written to throw a Redirect exception to get the OAuth2 authorization is as follows:
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
try {
logger.info("OAuth2 Filter Triggered!! for path {} {}", request.getRequestURI(), request.getRequestURL().toString());
logger.info("OAuth2 Filter hashCode {} request hashCode {}", this.hashCode(), request.hashCode());
String code = request.getParameter("code");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
logger.info("Code is {} and authentication is {}", code, authentication == null ? null : authentication.isAuthenticated());
// not authenticated
if (requiresRedirectForAuthentication(code)) {
URI authURI = new URI(googleAuthorizationUrl);
logger.info("Posting to {} to trigger auth redirect", authURI);
String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + oauth2RestTemplate.getAccessToken();
logger.info("Getting profile data from {}", url);
// Should throw RedirectRequiredException
oauth2RestTemplate.getForEntity(url, GoogleProfile.class);
// authentication in progress
return null;
} else {
logger.info("OAuth callback received");
// get user profile and prepare the authentication token object.
String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + oauth2RestTemplate.getAccessToken();
logger.info("Getting profile data from {}", url);
ResponseEntity<GoogleProfile> forEntity = oauth2RestTemplate.getForEntity(url, GoogleProfile.class);
GoogleProfile profile = forEntity.getBody();
CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(profile.getEmail());
authenticationToken.setAuthenticated(false);
Authentication authenticate = getAuthenticationManager().authenticate(authenticationToken);
logger.info("Final authentication is {}", authenticate == null ? null : authenticate.isAuthenticated());
return authenticate;
}
} catch (URISyntaxException e) {
Throwables.propagate(e);
}
return null;
}
The filter chain sequence from the Spring web app is as follows:
o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'metricFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'oauth2ClientContextFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'googleOAuthFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'org.springframework.security.filterChainProxy' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'applicationContextIdFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'webRequestLoggingFilter' to: [/*]
The redirect to Google works fine and I get the callback to the filter and the authentication is successful. However after that, the request results in a redirect and it invokes the filter again (the request is the same, I have checked the hasCode). On the second call the authentication in the SecurityContext is null. As part of the first authentication call the Authentication object was populated in the security context, so why does it disappear?
I am working with Spring Security for the first time so may have made newbie mistake.
After playing around with Spring Security configuration and the filters I was finally able to get this working. I had to make couple of important changes
I used a standard Spring OAuth2 filter (org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter) instead of the custom filter I was using.
Change the intercept URL of the authentication filter to be /googleLogin and added an authentication entry point which redirects to this URL on authentication failure.
Overall the flow is as follows
Browser accesses / and the request passes through the OAuth2ClientContextFilter and OAuth2ClientAuthenticationProcessingFilter as the context does not match. The configured context path for login is /googleLogin
The security interceptor FilterSecurityInterceptor detects that the the user is anonymous and throws an access denied exception.
Spring security's ExceptionTranslationFilter catches the access denied exception and asks the configured authentication entry point to handle it which issues a redirect to /googleLogin.
For the request /googleLogin, the filter OAuth2AuthenticationProcessingFilter tries to access the Google protected resource and an UserRedirectRequiredException is thrown which is translated into a HTTP redirect to Google (with the OAuth2 details) by OAuth2ClientContextFilter.
On successful authentication from Google the browser is redirected back to /googleLogin with the OAuth code. The filter OAuth2AuthenticationProcessingFilter handles this and creates an Authentication object and updates the SecurityContext.
At this point the user is fully authenticated and redirect to / is issued by the OAuth2AuthenticationProcessingFilter.
FilterSecurityInterceptor allows the request to proceed as the SecurityContext contains an Authentication object which is authenticated.
Finally the application page which is secured using an expression like isFullyAuthenticated() or similar is rendered.
The security context xml is as follows:
<sec:http use-expressions="true" entry-point-ref="clientAuthenticationEntryPoint">
<sec:http-basic/>
<sec:logout/>
<sec:anonymous enabled="false"/>
<sec:intercept-url pattern="/**" access="isFullyAuthenticated()"/>
<!-- This is the crucial part and the wiring is very important -->
<!--
The order in which these filters execute are very important. oauth2ClientContextFilter must be invoked before
oAuth2AuthenticationProcessingFilter, that's because when a redirect to Google is required, oAuth2AuthenticationProcessingFilter
throws a UserRedirectException which the oauth2ClientContextFilter handles and generates a redirect request to Google.
Subsequently the response from Google is handled by the oAuth2AuthenticationProcessingFilter to populate the
Authentication object and stored in the SecurityContext
-->
<sec:custom-filter ref="oauth2ClientContextFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
<sec:custom-filter ref="oAuth2AuthenticationProcessingFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</sec:http>
<b:bean id="oAuth2AuthenticationProcessingFilter" class="org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter">
<b:constructor-arg name="defaultFilterProcessesUrl" value="/googleLogin"/>
<b:property name="restTemplate" ref="googleRestTemplate"/>
<b:property name="tokenServices" ref="tokenServices"/>
</b:bean>
<!--
These token classes are mostly a clone of the Spring classes but have the structure modified so that the response
from Google can be handled.
-->
<b:bean id="tokenServices" class="com.rst.oauth2.google.security.GoogleTokenServices">
<b:property name="checkTokenEndpointUrl" value="https://www.googleapis.com/oauth2/v1/tokeninfo"/>
<b:property name="clientId" value="${google.client.id}"/>
<b:property name="clientSecret" value="${google.client.secret}"/>
<b:property name="accessTokenConverter">
<b:bean class="com.rst.oauth2.google.security.GoogleAccessTokenConverter">
<b:property name="userTokenConverter">
<b:bean class="com.rst.oauth2.google.security.DefaultUserAuthenticationConverter"/>
</b:property>
</b:bean>
</b:property>
</b:bean>
<!--
This authentication entry point is used for all the unauthenticated or unauthorised sessions to be directed to the
/googleLogin URL which is then intercepted by the oAuth2AuthenticationProcessingFilter to trigger authentication from
Google.
-->
<b:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<b:property name="loginFormUrl" value="/googleLogin"/>
</b:bean>
Also the Java Config for the OAuth2 resources is as follows:
#Configuration
#EnableOAuth2Client
class OAuth2SecurityConfiguration {
#Autowired
private Environment env;
#Resource
#Qualifier("accessTokenRequest")
private AccessTokenRequest accessTokenRequest;
#Bean
#Scope("session")
public OAuth2ProtectedResourceDetails googleResource() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("google-oauth-client");
details.setClientId(env.getProperty("google.client.id"));
details.setClientSecret(env.getProperty("google.client.secret"));
details.setAccessTokenUri(env.getProperty("google.accessTokenUri"));
details.setUserAuthorizationUri(env.getProperty("google.userAuthorizationUri"));
details.setTokenName(env.getProperty("google.authorization.code"));
String commaSeparatedScopes = env.getProperty("google.auth.scope");
details.setScope(parseScopes(commaSeparatedScopes));
details.setPreEstablishedRedirectUri(env.getProperty("google.preestablished.redirect.url"));
details.setUseCurrentUri(false);
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
private List<String> parseScopes(String commaSeparatedScopes) {
List<String> scopes = newArrayList();
Collections.addAll(scopes, commaSeparatedScopes.split(","));
return scopes;
}
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestTemplate googleRestTemplate() {
return new OAuth2RestTemplate(googleResource(), new DefaultOAuth2ClientContext(accessTokenRequest));
}
}
I had to override some of the Spring classes as the format of the token from Google and the one expected by Spring don't match. So there is some custom handiwork required there.
Not to impose a better answer, but another reason for this redirect looping is the session cookie handling when the session cookie sameSite attribute is set to Strict. Then, if the authority service has broken the redirect chain, the session cookie will not be transmitted.
See: How can I redirect after OAUTH2 with SameSite=Strict and still get my cookies?

Exception when saving securityContext to SecurityContextRepository

Started to secure some of my resful server resources using Spring Security.
My client is using ajax (jquery ajax) for the requests and I started by implementing the login functionality.
My Jersey web layer includes the following:
#Path("/login")
#Component
public class LoginResourceProvider extends ServiceResourceProvider {
/*--- Static ---*/
private final static ILogger logger = LogManager.getLogger(LoginResourceProvider.class);
/*--- Members ---*/
#Inject
#Qualifier("authenticationManager")
protected AuthenticationManager authenticationManager;
#Inject
protected SecurityContextRepository repository;
#Inject
protected RememberMeServices rememberMeServices;
/*--- Constructors ---*/
public LoginResourceProvider() {
super("Login");
}
/*--- Public Methods ---*/
#GET
public void login() {
}
/**
* A user login attempt
*
* #param username
* The user name
* #param password
* The password of the given user name
* #param request
* #param response
* #return A JSON string, indicating if the login is successful
*/
#POST
#Produces(MediaType.APPLICATION_JSON)
public String performLogin(#QueryParam("j_username") String username, #QueryParam("j_password") String password,
#Context HttpServletRequest request, #Context HttpServletResponse response) {
// Create a token
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
SecurityContext securityContext = SecurityContextHolder.getContext();
try {
// Attempting to authenticate the user
Authentication auth = authenticationManager.authenticate(token);
// Updating the SecurityContext, which represents the user's
// secured, authenticated session
securityContext.setAuthentication(auth);
// If the user authenticates successfully then the authentication
// storing the security context in the HttpSession between requests
repository.saveContext(securityContext, request, response);
// object is passed to the remember-me service
rememberMeServices.loginSuccess(request, response, auth);
// Successfully authenticated
return "{\"status\": true}";
// Bad Credentials
} catch (BadCredentialsException ex) {
return "{\"status\": false, \"error\": \"Bad Credentials\"}";
}
}
}
My security-context.xml is pretty basic for now, just enough to test my the login process:
<http use-expressions="true">
<form-login />
<remember-me />
<intercept-url pattern="/**" access="permitAll" />
<intercept-url pattern="/secured/**" access="isAuthenticated()" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
I have 2 questions:
Is it a good practice? I mean, I could not find lot's of "non-auto" login for ajax style requests there.
I'm getting an exception when trying to save the security context to the SecurityContextRepository, in this line:
repository.saveContext(securityContext, request, response);
When I'm trying to log in using bob as username and bobspassword password the authentication goes smoothly but while debugging this specific line I'm jumping to a ClassCastException with the message:
$Proxy31 cannot be cast to org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper
Any help is appreciated!
Ok, I think I got it.
According to Spring documentation authentication is done using the following steps:
The username and password are obtained and combined into an instance of UsernamePasswordAuthenticationToken (an instance of the Authentication interface, which we saw earlier).
The token is passed to an instance of AuthenticationManager for validation.
The AuthenticationManager returns a fully populated Authentication instance onsuccessful authentication.
The security context is established by calling SecurityContextHolder.getContext().setAuthentication(...) , passing in the returned authentication object.
In addition to the above steps, I also tried to store the SecurityContext in between requests by saving it to the SecurityContextRepository.
The responsibility for storing the SecurityContext between requests should fall to the SecurityContextPersistenceFilter which in it's turn invokes this operation, so no need for me to do it manually, I guess I should only stick to the above 4 steps.
UPDATE: I guess I tried implementing on my own something that Spring-Security already implements for me. I do not recommend following this approach, Spring-Security offers a much more simple practice.

Resources