Logout/Session timeout catching with spring security - java

I'm using spring/spring-security 3.1 and want to take some action whenever the user logs out (or if the session is timed out). I managed to get the action done for logout but for session timeout, I can't get it working.
In web.xml I only have the ContextLoaderListener specified ( can this be the issue? ) and of course the DelegatingFilterProxy.
I use the auto config like this.
<security:http auto-config="false" use-expressions="false">
<security:intercept-url pattern="/dialog/*"
access="ROLE_USERS" />
<security:intercept-url pattern="/boa/*"
access="ROLE-USERS" />
<security:intercept-url pattern="/*.html"
access="ROLE-USERS" />
<security:form-login login-page="/auth/login.html"
default-target-url="/index.html" />
<security:logout logout-url="/logout"
invalidate-session="true"
delete-cookies="JSESSIONID" success-handler-ref="logoutHandler" />
</security:http>
<bean id="logoutHandler" class="com.bla.bla.bla.LogoutHandler">
<property name="logoutUrl" value="/auth/logout.html"/>
</bean>
The logout handler is called when user clicks logout, which will make some calls to a database.
But how do I handle the session timeout ???
One way to handle it would be to inject the username into the session when user logs in and then use an ordinary httpsessionlistener and do the same thing on session timeout.
Is there a similar way with spring security, so that when spring discovers that the session is to timeout, I can hook in there, access the Authentication and get the UserDetails from there and do the clean up.

I've got a simpler solution. This works for both logout and session timeout.
#Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event)
{
List<SecurityContext> lstSecurityContext = event.getSecurityContexts();
UserDetails ud;
for (SecurityContext securityContext : lstSecurityContext)
{
ud = (UserDetails) securityContext.getAuthentication().getPrincipal();
// ...
}
}
}
web.xml:
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

Ok, I got a solution working, it's not as good as I'd like, but it get's me to the result.
I create a bean from which I can get a hold of the ApplicationContext.
public class AppCtxProvider implements ApplicationContextAware {
private static WeakReference<ApplicationContext> APP_CTX;
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
APP_CTX = new WeakReference<ApplicationContext>(applicationContext);
}
public static ApplicationContext getAppCtx() {
return APP_CTX.get();
}
}
I implement HttpSessionEventPublisher and on destroy, i get the UserDetails via sessionRegistry.getSessionInfo(sessionId)
Now I have the spring beans which I need to do the cleanup of the session and the user for whom the session timed out for.
public class SessionTimeoutHandler extends HttpSessionEventPublisher {
#Override
public void sessionCreated(HttpSessionEvent event) {
super.sessionCreated(event);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
SessionRegistry sessionRegistry = getSessionRegistry();
SessionInformation sessionInfo = (sessionRegistry != null ? sessionRegistry
.getSessionInformation(event.getSession().getId()) : null);
UserDetails ud = null;
if (sessionInfo != null) {
ud = (UserDetails) sessionInfo.getPrincipal();
}
if (ud != null) {
// Do my stuff
}
super.sessionDestroyed(event);
}
private SessionRegistry getSessionRegistry() {
ApplicationContext appCtx = AppCtxProvider.getAppCtx();
return appCtx.getBean("sessionRegistry", SessionRegistry.class);
}

You can use SimpleRedirectInvalidSessionStrategy to redirect to a URL when an invalid requested session is detected by the SessionManagementFilter.
Sample applicationContext would be like this:
<http>
<custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<http>
<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
<beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
<beans:property name="invalidSessionStrategy" ref="simpleRedirectInvalidSessionStrategy " />
</beans:bean>
<beans:bean id="simpleRedirectInvalidSessionStrategy" class="org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/general/logins/sessionExpired.jsf" />
<beans:property name="createNewSession" value="false" />
</beans:bean>
<beans:bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>
If you are using JSF, also refer to JSF 2, Spring Security 3.x and Richfaces 4 redirect to login page on session time out for ajax requests on how to handle Ajax requests as well.
UPDATE: In such a case, you can extend the HttpSessionEventPublisher and listen for sessionDestroyed events like this:
package com.examples;
import javax.servlet.http.HttpSessionEvent;
import org.springframework.security.web.session.HttpSessionEventPublisher;
public class MyHttpSessionEventPublisher extends HttpSessionEventPublisher {
#Override
public void sessionCreated(HttpSessionEvent event) {
super.sessionCreated(event);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
//do something
super.sessionDestroyed(event);
}
}
and then register this listener in your web.xml like this:
<listener>
<listener-class>com.examples.MyHttpSessionEventPublisher</listener-class>
</listener>

While session getting created at the time of one microservice application with setMaxInactive interval to 60 sec suppose . Session gets timedout after 60sec but after using your suggested change it should come to sessionDestroyed event but still it gets under session created and session again doesnot gets invalidated.

Related

Spring-websockets : Spring security authorization not working inside websockets

I am working on a Spring-MVC application in which we have Spring-security for authentication and authorization. We are working on migrating to Spring websockets, but we are having an issue with getting the authenticated user inside a websocket connection. The security context simply doesn't exist in the websocket connection, but works fine with regular HTTP. What are we doing wrong?
WebsocketConfig :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/app").withSockJS();
}
}
In the controller below, we are trying to get the currently authenticated user and it's always null
#Controller
public class OnlineStatusController extends MasterController{
#MessageMapping("/onlinestatus")
public void onlineStatus(String status) {
Person user = this.personService.getCurrentlyAuthenticatedUser();
if(user!=null){
this.chatService.setOnlineStatus(status, user.getId());
}
}
}
security-applicationContext.xml :
<security:http pattern="/resources/**" security="none"/>
<security:http pattern="/org/**" security="none"/>
<security:http pattern="/jquery/**" security="none"/>
<security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true">
<security:form-login login-page="/login" username-parameter="j_username" password-parameter="j_password"
login-processing-url="/j_spring_security_check" default-target-url="/canvaslisting"
always-use-default-target="false" authentication-failure-url="/login?error=auth"/>
<security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService"
token-validity-seconds="1209600" data-source-ref="dataSource"/>
<security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/>
<security:csrf disabled="true"/>
<security:intercept-url pattern="/cometd/**" access="permitAll" />
<security:intercept-url pattern="/app/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')" />
<!-- <security:intercept-url pattern="/**" requires-channel="https"/>-->
<security:port-mappings>
<security:port-mapping http="80" https="443"/>
</security:port-mappings>
<security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/>
<security:session-management session-fixation-protection="newSession">
<security:concurrency-control session-registry-ref="sessionReg" max-sessions="5" expired-url="/login"/>
</security:session-management>
</security:http>
I remember stumbling across the very same problem in a project I was working on. As I could not figure out the solution using the Spring documentation - and other answers on Stack Overflow were not working for me - I ended up creating a workaround.
The trick is essentially to force the application to authenticate the user on a WebSocket connection request. To do that, you need a class which intercepts such events and then once you have control of that, you can call your authentication logic.
Create a class which implements Spring's ChannelInterceptorAdapter. Inside this class, you can inject any beans you need to perform the actual authentication. My example uses basic auth:
#Component
public class WebSocketAuthInterceptorAdapter extends ChannelInterceptorAdapter {
#Autowired
private DaoAuthenticationProvider userAuthenticationProvider;
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException {
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
StompCommand cmd = accessor.getCommand();
if (StompCommand.CONNECT == cmd || StompCommand.SEND == cmd) {
Authentication authenticatedUser = null;
String authorization = accessor.getFirstNativeHeader("Authorization:");
String credentialsToDecode = authorization.split("\\s")[1];
String credentialsDecoded = StringUtils.newStringUtf8(Base64.decodeBase64(credentialsToDecode));
String[] credentialsDecodedSplit = credentialsDecoded.split(":");
final String username = credentialsDecodedSplit[0];
final String password = credentialsDecodedSplit[1];
authenticatedUser = userAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(username, password));
if (authenticatedUser == null) {
throw new AccessDeniedException();
}
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
accessor.setUser(authenticatedUser);
}
return message;
}
}
Then, in your WebSocketConfig class, you need to register your interceptor. Add the above class as a bean and register it. After these changes, your class would look like this:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Autowired
private WebSocketAuthInterceptorAdapter authInterceptorAdapter;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/app").withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(authInterceptorAdapter);
super.configureClientInboundChannel(registration);
}
}
Obviously, the details of the authentication logic are up to you. You can call a JWT service or whatever you are using.
If you are using SockJS + Stomp and configured your security correctly, you should be able to connect via regular username/pw authenticator like #AlgorithmFromHell and do
accessor.setUser(authentication.getPrincipal()) // stomp header accessor
You can also connect via http://{END_POINT}/access_token={ACCESS_TOKEN}. Spring security should be able to pick it and do loadAuthentication(access_token) via ResourceServerTokenServices. When this is done, you can get your principal by adding this to your impl of AbstractSessionWebSocketMessageBrokerConfigurer or WebSocketMessageBrokerConfigurer. When doing this, for some reason, the loaded Pricipal is saved in "simpUser" header instead.
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) {
if (message.getHeaders().get("simpUser") != null && message.getHeaders().get("simpUser") instanceof OAuth2Authentication) { // or Authentication depending on your impl of security
OAuth2Authentication authentication = (OAuth2Authentication) message.getHeaders().get("simpUser");
accessor.setUser(authentication != null ? (UserDetails) authentication.getPrincipal() : null);
}
}
return message;
}
});
}

How to Intercept Spring Security login request before its processing?

I am trying to intercept the login request before spring security start its processing or it reaches to spring security interceptor. I want to do this because I have to do some validations on the user's input. So, I have created a interceptor from HandlerInterceptorAdapter class but it is not working.
So what I want to do is :
When any user try to login with username and password then this request must reaches to LoginRequestInterceptor first. Do some validation on user inputs and on success pass this request to spring security login processing url other wise to some other url.
But in my case the request is reaching directly to the spring security without visiting the interceptor.
Interceptor class
public class LoginRequestInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LogManager.getLogger(LoginRequestInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.debug("Interceptor working pre");
return super.preHandle(request, response, handler);
}
Spring Security xml
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/Candidate/**" access="hasRole('ROLE_USER')" />
<!-- access denied page -->
<access-denied-handler error-page="/403" />
<form-login
login-processing-url="/login"
login-page="/"
default-target-url="/Candidate"
authentication-failure-url="/error=1"
username-parameter="registerationid"
password-parameter="password"
/>
<logout logout-success-url="/?logout" />
<custom-filter ref="loginRequestInterceptors" position="FIRST"/>
<!-- enable csrf protection -->
<csrf />
</http>
<authentication-manager id="authenticationManager">
<authentication-provider user-service-ref="candidateDetailServices" />
</authentication-manager>
Spring dispatcher xml
<bean id="loginRequestInterceptor" class="org.ibps.clerk.inteceptors.login.LoginRequestInterceptor"></bean>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/login"/>
<ref bean="loginRequestInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
So at last I wanted to know whether it is possible or not? If Yes then please share the link or solution.
You should create a class (for example CustomFilter) extend UsernamePasswordAuthenticationFilter and over ride attemptAuthentication and do your stuff in it and then call super.attemptAuthentication;
Then you should confgiure in your security config class which extends WebSecurityConfigurerAdapter.
#Configuration
#EnableWebSecurity
public class SecurityConfigs extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getCustomFilter()
,UsernamePasswordAuthenticationFilter.class)
.formLogin()
.and() // more configs ...
}
public CustomFilter getCustomFilter() throws Exception {
CustomFilter filter= new CustomFilter ("/loginuser","POST");
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler((request, response, exception) -> {
response.sendRedirect("/login?error");
});
return filter;
}
And your CustomFilter class:
public class CustomFilter extends UsernamePasswordAuthenticationFilter {
public CustomFilter (String urlLogin, String method) {
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(urlLogin, method));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// do your stuff ...
return super.attemptAuthentication(request, response);
}
}
What you're probably looking to do is create a servlet filter that is executed before the Spring Security filterchain. By default the springSecurityFilterChain filter's order value is set to 0, meaning that it is executed before all other filters. One workaround for this is to set the security.filter-order to a higher value than that of the filter you wish to run before its execution in your properties file.
Check Filter order in spring-boot for further discussion on this topic.
Additional resources:
https://github.com/spring-projects/spring-security-oauth/issues/1024
https://mtyurt.net/2015/07/15/spring-how-to-insert-a-filter-before-springsecurityfilterchain/

How Spring Security's custom login works

I'm trying to get through Spring Security. I have to implement a custom login form, so I need to understand very well what my configurations mean.
spring-security.xml
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="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-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd">
<http auto-config="true">
<intercept-url pattern="/user**" access="isAuthenticated()" />
<form-login authentication-failure-url="/login" login-page="/login"
login-processing-url="/login" default-target-url="/user" />
<logout invalidate-session="true" logout-success-url="/index"
logout-url="/logout" />
</http>
<authentication-manager id="custom-auth">
<authentication-provider>
<user-service>
<user name="my_username" password="my_password"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
LoginController
#Controller
public class LoginController {
[....]
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView doLogin() {
System.out.println("***LOGIN_POST***");
return new ModelAndView("users/home");
}
#RequestMapping(value = "/logout", method = RequestMethod.POST)
public ModelAndView doLogout() {
System.out.println("***LOGOUT_POST***");
return new ModelAndView("index");
}
}
I know I can map the /login URL with RequestMethod.GET, but when I try to intercept the POST after form submit it doesn't work.
I believe, but need to confirm, that is because Security is doing
something behind the scenes: gets username and password values from
the posted form and compare them with the ones in the authentication
provider: if these match, default-target-url is shown, else user
must repeat the login. Is it right?
Then my problem is: I need username and password values typed in the
Security's login form, because I have to send an HTTP request to an
external server to verify if these match. Before to introduce
Security I developed this mechanism using /login GET and /login
POST, with #ModelAttribute annotation. How can I do now?
Changing the authentication-provider, using a class which implements UserDetailsService, what happens? I believe that, in this case, username and password typed in the login form are compared with the ones retrieved from the db, as these are assigned to the User object. Is it right?
UserDetailsServiceImpl
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private CustomerDao customerDao;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
Customer customer = customerDao.findCustomerByUsername(username);
return new User(customer.getUsername(), customer.getPassword(), true, true, true, true,
Arrays.asList(new SimpleGrantedAuthority(customer.getRole())));
}
}
N.B.
User's data are not in my db at first, that's because I'm not sure about the UserDetailsService solution (in which UserDetails are loaded simply by username). To retrieve my Customer object I need both username and password (to send to a specific external URL) then, if the JSON response is positive (username and password are correct), I have to send 2 others HTTP request to get Customer's data as firstname, lastname, nationality, etc. At this point my user can be considered logged in.
Any suggestions?
Thanks in advance.
I believe, but need to confirm, that is because Security is doing
something behind the scenes: gets username and password values from
the posted form and compare them with the ones in the authentication
provider: if these match, default-target-url is shown, else user
must repeat the login. Is it right?
That's right. When you declare a <login-form> element in the security config you are configuring a UsernamePasswordAuthenticationFilter.
There you configure some url's:
login-page="/login" : The url which points to a #RequestMapping which returns the login form
login-processing-url="/login": The url wich triggers the UsernamePasswordAuthenticationFilter. This is in spring-security the equivalent to build a post processing controller method.
default-target-url="/user": the default page where the user will be redirected after providing a valid user credentials.
authentication-failure-url="/login" : The url where the user will be redirected in case of trying to login with invalid credentials.
While login-processing-url, default-target-url and authentication-failure-url must be valid RequestMappings, the login-processing-url won't reach the Spring MVC controller layer as it is executed before hitting the Spring MVC dispatcher servlet.
So the
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView doLogin() {
System.out.println("***LOGIN_POST***");
return new ModelAndView("users/home");
}
won't be never reached.
When a POST is made to /login uri, the UsernamePasswordAuthenticationFilter will perform it's doFilter() method to catch the user provided credentials, build a UsernamePasswordAuthenticationToken and delegate it to the AuthenticationManager, where this Authentication will be executed in the matching AuthenticationProvider.
Then my problem is: I need username and password values typed in the Security's login form, because I have to send an HTTP request to an external server to verify if these match. Before to introduce Security I developed this mechanism using /login GET and /login POST, with #ModelAttribute annotation. How can I do now?
I suppose when you used to perform the authentication to the external server you did it by delegating to a class from the POST /login RequestMapping.
So simply create a custom AuthenticationProvider which delegates the user validation stuff to your old logic:
public class ThirdPartyAuthenticationProvider implements AuthenticationProvider {
private Class<? extends Authentication> supportingClass = UsernamePasswordAuthenticationToken.class;
// This represents your existing username/password validation class
// Bind it with an #Autowired or set it in your security config
private ExternalAuthenticationValidator externalAuthenticationValidator;
/* (non-Javadoc)
* #see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
*/
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
boolean validated = this.externalAuthenticationValidator.validate(authentication.getName(), authentication.getCredentials().toString());
if(!validated){
throw new BadCredentialsException("username and/or password not valid");
}
Collection<? extends GrantedAuthority> authorities = null;
// you must fill this authorities collection
return new UsernamePasswordAuthenticationToken(
authentication.getName(),
authentication.getCredentials(),
authorities
);
}
/* (non-Javadoc)
* #see org.springframework.security.authentication.AuthenticationProvider#supports(java.lang.Class)
*/
#Override
public boolean supports(Class<?> authentication) {
return this.supportingClass.isAssignableFrom(authentication);
}
public ExternalAuthenticationValidator getExternalAuthenticationValidator() {
return externalAuthenticationValidator;
}
public void setExternalAuthenticationValidator(ExternalAuthenticationValidator externalAuthenticationValidator) {
this.externalAuthenticationValidator = externalAuthenticationValidator;
}
}
And the security config xml:
<beans:bean id="thirdPartyAuthenticationProvider" class="com.xxx.yyy.ThirdPartyAuthenticationProvider">
<!-- here set your external authentication validator in case you can't autowire it -->
<beans:property name="externalAuthenticationValidator" ref="yourExternalAuthenticationValidator" />
</beans:bean>
<security:authentication-manager id="custom-auth">
<security:authentication-provider ref="thirdPartyAuthenticationProvider" />
</security:authentication-manager>
<security:http auto-config="true" authentication-manager-ref="custom-auth">
<security:intercept-url pattern="/user**" access="isAuthenticated()" />
<security:form-login authentication-failure-url="/login" login-page="/login"
login-processing-url="/login" default-target-url="/user" />
<security:logout invalidate-session="true" logout-success-url="/index"
logout-url="/logout" />
<!-- in spring security 4.x CSRF filter is enabled by default. Disable it if
you don't plan to use it, or at least in the first attempts -->
<security:csrf disabled="true"/>
</security:http>
Changing the authentication-provider, using a class which implements UserDetailsService, what happens? I believe that, in this case, username and password typed in the login form are compared with the ones retrieved from the db, as these are assigned to the User object. Is it right?
As you said that you must send both username and password, I don't think that UserServiceDetails schema fits your requirements. I thin you should do it as I suggested in point 2.
EDIT:
One last thing: now I'm sending HTTP request in authenticate method,
if credentials are correct I receive a token in the response, which I
need to get access to other external server services. How can I pass
it in my Spring controller?
To receive and handle the received token, I would do it like this:
The ExternalAuthenticationValidator interface:
public interface ExternalAuthenticationValidator {
public abstract ThirdPartyValidationResponse validate(String name, String password);
}
The ThirdPartyValidationResponse model interface:
public interface ThirdPartyValidationResponse{
public boolean isValid();
public Serializable getToken();
}
Then, change the way the Provider handles and manages it:
public class ThirdPartyAuthenticationProvider implements AuthenticationProvider {
private Class<? extends Authentication> supportingClass = UsernamePasswordAuthenticationToken.class;
private ExternalAuthenticationValidator externalAuthenticationValidator;
/* (non-Javadoc)
* #see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
*/
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
ThirdPartyValidationResponse response = this.externalAuthenticationValidator.validate(authentication.getName(), authentication.getCredentials().toString());
if(!response.isValid()){
throw new BadCredentialsException("username and/or password not valid");
}
Collection<? extends GrantedAuthority> authorities = null;
// you must fill this authorities collection
UsernamePasswordAuthenticationToken authenticated =
new UsernamePasswordAuthenticationToken(
authentication.getName(),
authentication.getCredentials(),
authorities
);
authenticated.setDetails(response);
return authenticated;
}
/* (non-Javadoc)
* #see org.springframework.security.authentication.AuthenticationProvider#supports(java.lang.Class)
*/
#Override
public boolean supports(Class<?> authentication) {
return this.supportingClass.isAssignableFrom(authentication);
}
public ExternalAuthenticationValidator getExternalAuthenticationValidator() {
return externalAuthenticationValidator;
}
public void setExternalAuthenticationValidator(ExternalAuthenticationValidator externalAuthenticationValidator) {
this.externalAuthenticationValidator = externalAuthenticationValidator;
}
}
Now, you must use this code snippet to retrieve the token from the userDetails:
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
if(auth == null){
throw new IllegalAccessException("Authentication is null in SecurityContext");
}
if(auth instanceof UsernamePasswordAuthenticationToken){
Object details = auth.getDetails();
if(details != null && details instanceof ThirdPartyValidationResponse){
return ((ThirdPartyValidationResponse)details).getToken();
}
}
return null;
Instead of including it everywhere you need it, it may be better to create a class which retrieves it from the details of the authentication:
public class SecurityContextThirdPartyTokenRetriever {
public Serializable getThirdPartyToken() throws IllegalAccessException{
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
if(auth == null){
throw new IllegalAccessException("Authentication is null in SecurityContext");
}
if(auth instanceof UsernamePasswordAuthenticationToken){
Object details = auth.getDetails();
if(details != null && details instanceof ThirdPartyValidationResponse){
return ((ThirdPartyValidationResponse)details).getToken();
}
}
return null;
}
}
In case you chose this last way, just declare it in the security xml config (or annotate with a #Service, etc annotation):
<beans:bean id="tokenRetriever" class="com.xxx.yyy.SecurityContextThirdPartyTokenRetriever" />
There are other approaches, such as extending UsernamePasswordAuthenticationToken to include the token as a field on it, but this is the easiest one I think.

Spring RequestMapping from Resources and PropertyPlaceholderConfigurer?

I'm trying to do a RequestMapping for URL from the Resources file to be variable according to the current Locale
I tried to use the PlaceHolders but i know it should load from Properties files . in addition to i have to load it as Bean during the run time thus it will load one time only with the default Locale so even if the changed the Locale , it will keep loading from the default Locale > en_US
Any Ideas ?
My Tries :
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
setProperties(convertResourceBundleToProperties(ResourceBundle.getBundle("urls", LocaleContextHolder.getLocale())));
super.postProcessBeanFactory(beanFactory);
}
}
and calling at in a Bean :
#Bean
public CustomPropertyPlaceholderConfigurer CustomPropertyPlaceholderConfigurer(){
return new CustomPropertyPlaceholderConfigurer();
}
Resources urls_ab.properties:
url.controller1=test
Controller :
#RequestMapping(value = "/${url.controller1}", method = RequestMethod.GET)
public String dd(ModelMap model){
return "__front_container";
}
When you make a change to your properties files that back your PropertyPlaceholderConfigurer, you will need to 'refresh' your application for changes to take effect. If you use a ConfigurableApplicationContext as your context, then you may call refresh on your context. The challenge is that in a web application, you will depend on your web.xml and not directly on the context object so refreshing to load the new/updated properties would require an application restart...or going through many unnecessary hoops. Consider the below which is an example within a Spring Webflow application. The locale is updated via the use of an interceptor. :
public class MyLocaleChangeInterceptor extends org.springframework.web.servlet.i18n.LocaleChangeInterceptor {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
if (locale != null) {
try {
response.setLocale(locale);
} catch (Exception ex) {
response.setLocale(Locale.ENGLISH);
}
} else {
response.setLocale(Locale.ENGLISH);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
super.postHandle(request, response, handler, modelAndView);
}
}
/** https://gist.github.com/jkuipers/3537965 Spring LocaleResolver that uses cookies but falls back to the HTTP Session when cookies are disabled*/
public class MyCookieLocaleResolver extends CookieLocaleResolver {
private SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
#Override
protected Locale determineDefaultLocale(HttpServletRequest request) {
return sessionLocaleResolver.resolveLocale(request);
}
#Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
if (locale != null) {
try {
response.setLocale(locale);
} catch (Exception ex) {
response.setLocale(Locale.ENGLISH);
}
} else {
response.setLocale(Locale.ENGLISH);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
super.setLocale(request, response, locale);
sessionLocaleResolver.setLocale(request, response, locale);
}
#Override
public void setDefaultLocale(Locale defaultLocale) {
sessionLocaleResolver.setDefaultLocale(defaultLocale);
}
}
<!--Then the XML:-->
<bean id="localeChangeInterceptor" class="MyLocaleChangeInterceptor">
<property name="paramName" value="lang"/>
</bean>
<!-- Saves a locale change using a cookie -->
<bean id="localeResolver" class="MyCookieLocaleResolver" >
<property name="defaultLocale" value="en" />
</bean>
<!--Then Spring-webflow specific XML settings:-->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="order" value="2"/>
<property name="flowRegistry" ref="flowRegistry" />
<property name="interceptors" >
<list>
<ref local="localeChangeInterceptor" />
</list>
</property>
<property name="defaultHandler">
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
</property>
</bean>
If using Spring MVC (without spring webflow), see here for a brilliant solution: Spring MVC LocaleChangeInterceptor annotation based doesn't work
MyKong also provides a good solution: http://www.mkyong.com/spring-mvc/spring-mvc-internationalization-example/

Spring Security remember-me with mongodb and Spring Data

I try to use Spring Security to protected my app. Login code is as follow
//get UserDetails ....
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
Now I want to make Spring Security remember the user login, but i want do it using my code.
Please show me how.
Thanks!
I try to use a simple way. I use the TokenBasedRememberMeServices to do the remember me.
<bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailService"></property>
<property name="key" value="myRememberMeKey"></property>
<property name="alwaysRemember" value="true"></property>
</bean>
And the is
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<form-login login-processing-url="/j_spring_security_check"
login-page="/login.jsp"
authentication-failure-url="/login.jsp"/>
<remember-me services-ref="rememberMeServices" />
</http>
The userDetailService is
public class MyUserDetailService implements UserDetailsService {
#Resource
private UserDao userDaoImp;
#Override
public UserDetails loadUserByUsername(String userName)
throws UsernameNotFoundException {
User user = new User();
user.setName(userName);
List<User>ulist = userDaoImp.find(user);
if(ulist.size() == 1){
return (UserDetails) (ulist.toArray())[0];
}
return null;
}
}
When I login from the login page there is a cookie : SPRING_SECURITY_REMEMBER_ME_COOKIE
But when I close the webbrowser and open it again, it still turn into the login page. And it set the SPRING_SECURITY_REMEMBER_ME_COOKIE "".
Is there anything I did wrong?

Categories