Keep locale on Spring Security session timeout - spring

I'm using Spring Security 3.0.2 and I have this config
<security:form-login
login-processing-url="/resources/j_spring_security_check"
login-page="/login"
authentication-failure-handler-ref="authErrorHandler" authentication-success-handler-ref="authCorrectHandler" />
<security:logout logout-success-url="/index.jsp" invalidate-session="false" />
<security:remember-me />
<security:session-management invalid-session-url="/login/sessionExpired" >
<security:concurrency-control max-sessions="1"
error-if-maximum-exceeded="true" />
</security:session-management>
When I login with a certain locale, all went well but when expiring session, Spring Security clear session and create new anonymous session with locale by default (and go to the login page as expected). The result is the user locale was LOST.
How can I keep the user locale when expiring session in Spring Security 3.0.2?
I'm using localeChangeInterceptor to set the locale, like this:
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="language" />
</bean>
and SessionLocaleResolver as locale resolver:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="es" />
</bean>
EDIT - SOLVED FOR MY NEEDS
I've solved this finally setting a cookie in my own LocaleChangeInterceptor which extends from HandlerInterceptorAdapter, writing this in preHandle method:
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found.");
}
Cookie mylang = new Cookie("mylang", locale.getLanguage() + "_" + locale.getCountry());
mylang.setMaxAge(86400);
response.addCookie(mylang);
localeResolver.setLocale(request, response, locale);
and then in the /sessionExpired controller point, I'm getting the cookie value:
public String sessionExpired(HttpServletRequest request, HttpServletResponse response, Model model,
#CookieValue(value = "mylang", defaultValue = "es_ES") String myLang) throws Exception {
model.addAttribute("mylang", myLang);
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
StringTokenizer st = new StringTokenizer(myLang, "_");
String language = "";
String country = "";
try {
language = (String) st.nextElement();
country = (String) st.nextElement();
} catch (Exception e) {
throw new Exception("Error locale");
}
Locale locale = new Locale(language, country);
localeResolver.setLocale(request, response, locale);
return "sessionExpired";
No need to use database as temporal storage in this case.

Logically it does not look like you can use the session to keep track of the locale. The locale is set using a request param language which will be cleared with the session. When a new session is created the locale is defaulted to es as given by your localeResolver. One way I could think is to store user preferences in a DB and retrieve from there on subsequent log-ins
Also as suggested by #M. Deinum:
If you are ok with cookies use the CookieLocaleResolver which persists the choice in a cookie. Else implement your own LocaleResolver which stores/retrieves from database.

Stumbled upon this old question and just thought of sharing how I got away with my predicament using a Filter (would've preferred an Interceptor but for some reason it annoyingly ain't working for me as of this writing).
public class SessionTimeoutFilter implements Filter {
private String contextPath;
private String language;
#Override
public void init(FilterConfig config) {
contextPath = config.getInitParameter("context.path");
}
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String path = request.getRequestURI();
if (path.equals(contextPath)) { //the index page
request.getSession(true);
chain.doFilter(request, response);
} else {
HttpSession session = request.getSession(false);
if (session != null) {
language = (String) session.getAttribute("lang");
chain.doFilter(request, response);
} else {
response.sendRedirect(contextPath + "?timeout=true&lang=" + language);
}
}
}
}
And on top of a LocaleChangeInterceptor, I had parameters in my Controller.
#GetMapping("/")
public String home(#RequestParam(value = "timeout", required = false) Boolean timeout,
#RequestParam(value = "lang", required = false) String language,
HttpServletRequest request, Model model) {
if (Strings.isNotBlank(language)) {
LocaleContextHolder.setLocale(new Locale(language));
} else {
language = LocaleContextHolder.getLocale().getLanguage();
}
if (BooleanUtils.isTrue(timeout)) {
String message = messageService.getMessage("error.timeout", null);
model.addAttribute("message", message);
}
request.getSession().setAttribute("lang", language);
return "index";
}

Related

Not getting login failure reason (only BadCredential Exception is popped)

Tried various ways to get custom message from spring, if user authentication fails.
Using
<spring.version>4.2.4.RELEASE</spring.version>
<spring.security.version>4.0.3.RELEASE</spring.security.version>
XML configuration
<http auto-config="true" use-expressions="false">
<intercept-url pattern="/**" access='ROLE_FUNCTION' />
<form-login login-page="/login"
default-target-url="/welcome"
username-parameter="j_username"
password-parameter="j_password"
login-processing-url="/j_spring_security_check"
always-use-default-target="true"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-handler-ref="authenticationFailureHandler"
/>
<logout logout-url="/j_spring_security_logout" logout-success-url="/login?logout" delete-cookies="JSESSIONID" />
<access-denied-handler error-page="/accessDenied" />
<csrf disabled="true"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="**userDetailsService**">
<password-encoder ref="bcryptEncoder"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="authenticationSuccessHandler" class="com.company.project.CustomAuthenticationSuccessHandler"/>
<beans:bean id="**authenticationFailureHandler**" class="com.company.project.CustomAuthenticationFailureHandler"/>
<beans:bean name="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
Bean definition excerpt is as below
Implementation
userDetailsService
#Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("Getting access details for user : {}", username);
UserDto userDto = null;
boolean accountNonExpired = true;
boolean accountNonLocked = true;
boolean credentialsNonExpired = true;
boolean enabled = true;
try {
userDto = userService.loginUser(username);
if (userDto == null) {
throw new UsernameNotFoundException("User not found");
}
if (Active.Y != userDto.getActive()) {
enabled = false;
throw new BadCredentialsException("User account is inactive");
}
} catch (BaseException be) {
throw new BadCredentialsException(be.getMessage().toLowerCase());
}
UserContext context = new UserContext();
context.setLoginId(username);
context.setName(userDto.getName());
context.setPrincipleId(userDto.getId());
List<GrantedAuthority> grantedAuthorities = getGrantedAuthorities(userDto);
String password = getActivePassword(userDto);
accountNonExpired = isAccountActive(userDto);
accountNonLocked = isAccountUnlocked(userDto);
credentialsNonExpired = isCredentialsActive(userDto);
return new UserLoginDetails(grantedAuthorities, password, username, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled, context);
}
}
authenticationSuccessHandler works fine.
authenticationFailureHandler
#Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
UserService userService;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
try {
// execute it when user enters wrong password, i.e loginAttempt ...
} catch (Exception e) {
// TODO: something
}
// TODO: how do I send message, if authenticationException.
redirectStrategy.sendRedirect(request, response, "/login?error");
// clearAuthenticationAttributes(request);
}
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
To show error message I'm using following line
JSP
<c:set var="errorMessage" value="${sessionScope[\"SPRING_SECURITY_LAST_EXCEPTION\"].message}" />
Let me brief the expected messages.
If user enters wrong credentials he should get
"Invalid credentials"
If user account is inactive he should get
"Your account is not active"
If user exceeded permissible no. of
attempt his account will get locked and he will get "Your account is
locked"
If my implementation is not correct please let me know what changes should be done.
If you want to override the AuthenticationFailureHandler, you can extend the SimpleUrlAuthenticationFailureHandler, it already has a method to save exception.
protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
if (forwardToDestination) {
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
} else {
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
}
}
}
When you save the exception to request or session, then you can get the message.
${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}

login intercepter do not work in spring

all. i was using spring4 in my project. and add and interceptor extends HandlerInterceptorAdapter, then overwrite prehandle method. but i found it does not work when i was doing spring mock test.
i have configure it in springmvc-servlet.xml , like this:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.suerpay.common.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
and here is code of LoginInteceptor:
public class LoginInterceptor extends HandlerInterceptorAdapter {
#Autowired
LoginServiceRedis loginServiceRedis;
#Autowired
UserServiceDB userServiceDB;
Logger logger = LoggerFactory.getLogger(getClass());
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.info("start login interceptor");
if (isLoginRequired(handler)) {
String ticket = request.getHeader(GlobalConstants.TICKET_HEADER);
if (StringUtils.isEmpty(ticket)) {
throw new UnAuthorizedException(ResultCodeConstants.USER_NOT_LOGIN);
}
String userName = loginServiceRedis.getUserNameByTicket(ticket);
Long userId = userServiceDB.getUserIdByName(userName);
if (null == userId) {
throw new UnAuthorizedException(ResultCodeConstants.USER_NOT_LOGIN);
}
ThreadContextHolder.setCurrentUserId(userId);
}
logger.info("finish login interceptor");
return true;
}
private boolean isLoginRequired(Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
if (null != loginRequired) {
return true;
}
return false;
}
}
i think i have do everything , but just can not get into breakpoint.
who can tell me why?:(

authentication filter was called repeatedly

I setup spring security for my rest apis. Here is a sample of my rest call,
GET: http://localhost:8081/dashboard/epic/data. When executing, filter, provider and eventual onAuthenticationSuccess are triggered. Here is the problem, instead of executing the rest url after authentication, it will go back to filter many times. For the second time, request.getRequestUrl will be http://localhost:8081/dashboard.
Here is my security-context.xml:
<http auto-config='false' authentication-manager-ref="authenticationManager" entry-point-ref="authenticationEntryPoint">
<intercept-url pattern="dashboard/**" access="ROLE_USER" />
<csrf disabled="true"/>
<custom-filter position="REMEMBER_ME_FILTER" ref="DashboardFilter"></custom-filter>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="DashboardAuthProvider"></authentication-provider>
</authentication-manager>
<beans:bean id="DashboardFilter" class="com.apple.store.dashboard.security.DashboardAuthFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationSuccessHandler">
<beans:bean class="com.apple.store.dashboard.security.LoginSuccessHandler">
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="authenticationEntryPoint" class="com.apple.store.dashboard.security.DashboardAuthEntryPoint">
</beans:bean>
<beans:bean id="DashboardAuthProvider" class="com.apple.store.dashboard.security.DashboardAuthProvider"> </beans:bean>
Here is my filter
public class DashboardAuthFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(DashboardAuthFilter.class);
public DashboardAuthFilter() {
//super("/j_spring_cas_security_check");
super("/**");
}
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
throws org.springframework.security.core.AuthenticationException, UnsupportedEncodingException {
logger.debug("Inside DashboardAuthFilter:attemptAuthentication method:");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth!=null ){
if (auth.isAuthenticated()){
logger.debug("Previously authenticated.isAuthenticated=true::: Auth details:" +auth);
return auth;
}
}
String _username = null;
String _password = null;
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
StringTokenizer st = new StringTokenizer(authHeader);
if (st.hasMoreTokens()) {
String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
try {
String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
logger.debug("Credentials: " + credentials);
int p = credentials.indexOf(":");
if (p != -1) {
_username = credentials.substring(0, p).trim();
_password = credentials.substring(p + 1).trim();
}
} catch (Exception e) {
}
}
}
}
else
System.out.println("request url is "+request.getRequestURL());
Authentication authResult = null;
try {
if( org.apache.commons.lang.StringUtils.isEmpty(_password)) {
throw new PreAuthenticatedCredentialsNotFoundException("No username:password..");
}
String credentials = "NA";
//String validateCookieDetails = correctAuthentication(AOSCookie, request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(_username+":"+_password, credentials);
authResult = getAuthenticationManager().authenticate(authRequest);
logger.debug("Attempted authentication: authResult ::" + authResult.toString());
} catch (org.springframework.security.core.AuthenticationException e) {
logger.error("AttemptAuthentication: Not Authenticated : AuthenticationException ....." + e.getMessage());
} catch (Exception e) {
logger.error("Exception occured during authentication....." + e.getMessage());
}
return authResult;
}
Here is my provider:
public class DashboardAuthProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(DashboardAuthProvider.class);
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
logger.debug("Inside DashboardAuthProvider: authenticate method +authentication=" + authentication);
Authentication auth =null;
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
try{
String[] principalStrArr = ((String)authentication.getPrincipal()).split(":");
//Convert the authentication principal object to a map
if (principalStrArr[0].equals("test1") && principalStrArr[1].equals("test1"))
{
String username = principalStrArr[0];
String password = principalStrArr[1];
final UserDetails principal = new AccessInfo(username, password, grantedAuths);
auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
logger.info("DashboardAuthProvider auth= " + auth);
}
else {
logger.info("Wrong credential");
return null;
}
}catch (Exception e){
logger.error(
"Exception occured in DashboardAuthProvider during authentication",
e);
}
return auth;
}
And here is my onAuthenticationSuccess:
public class LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
}

How To Handle session using Interceptor in Spring 3

I Want to redirect to login whenever the session goes invalid.My servelt.xml is as follows.
<mvc:interceptors>
<bean class="com.package.login.LoginIntercepter" />
</mvc:interceptors>
Interceptor :
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
HashMap details = (HashMap)session.getAttribute("details");
System.out.println("Pre-handle ::"+request.getRequestURI());
if(details!=null){
/*For Checking the Session
* -----Start-----*/
return true;
}else{
response.sendRedirect("Login");//Here Login is action Name which is Mapped in Login Controller
return false;
}
}
Login Controller :
#RequestMapping(value="/Login", method=RequestMethod.GET)
public String loginMethodWithoutaction(HttpServletRequest request,HttpServletResponse response)
{
String page="login";
HttpSession session = request.getSession();
HashMap details = (HashMap)session.getAttribute("details");
if(details!=null)
page = "redirect:/Home";
return page;
}
If Session is invalid then it has to redirect "login" page. otherwise it has to go Home Controller.But its not working.Whenever application get started, the message get printed multiple times and at the end it gives stack overflow.
Message is printing multiple times because when you redirect request is intercepted again and your session is still null because controller method is not executed yet.
You have to create a method that will set details attribute on successful login in your controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
String path = request.getRequestURI().substring(request.getContextPath().length());
if(path.equals("/Login")){
return true;
}
else if(session == null || session.getAttribute("details") == null) {
request.setAttribute("emassage", "login failed");
throw new LoginFailException("login failed");
}else{
return true;
}
}
LoginFailExceptionClass :
public class LoginFailException extends Exception{
private static final long serialVersionUID = 1L;
public LoginFailException(String message) {
super(message);
}
}
In Controller handle the exception and redirect to login fail page :
#ExceptionHandler(LoginFailException.class)
public String redirectToLogin(){
return "redirect:/login";
}
I think it is your Spring configuration which is not good. Try something like that :
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<ref bean="loginIntercepter"/>
</property>
You have to declare your loginItercepter in the spring file

Spring CookieLocaleResolver: set cookiePath

Is it possible to set cookiePath with the value of application name (automatically)?
For example I have a test.war so it will be available at bla.com/test/ so I want my cookie's path be /test/ and not / that is default value.
Thank you
When you create the CookieLocaleResolver you can set the path, but it will be hard coded.
Ex
<bean id="localeResolver" class="CookieLocaleResolver">
<property name="cookiePath" value="test" />
</bean>
Another possible solution is to override the LocaleResolver
public class MyCookieLocaleResolver extends CookieLocaleResolver {
#Override
public void setLocale(HttpServletRequest request,
HttpServletResponse response, Locale locale) {
if (locale != null) {
// Set request attribute and add cookie.
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
addCookie(response, locale.toString());
} else {
// Set request attribute to fallback locale and remove cookie.
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
determineDefaultLocale(request));
removeCookie(response);
}
}
public void addCookie(HttpServletRequest request,
HttpServletResponse response, String cookieValue) {
Cookie cookie = createCookie(request, cookieValue);
Integer maxAge = getCookieMaxAge();
if (maxAge != null) {
cookie.setMaxAge(maxAge);
}
if (isCookieSecure()) {
cookie.setSecure(true);
}
response.addCookie(cookie);
if (logger.isDebugEnabled()) {
logger.debug("Added cookie with name [" + getCookieName()
+ "] and value [" + cookieValue + "]");
}
}
protected Cookie createCookie(HttpServletRequest request, String cookieValue) {
Cookie cookie = new Cookie(getCookieName(), cookieValue);
if (getCookieDomain() != null) {
cookie.setDomain(getCookieDomain());
}
cookie.setPath(request.getContextPath());
return cookie;
}
}
If you use a ServletContainer >= 2.5 and spring annotation, you can use the following code to get the ContextPath, so you don't have to hardcode:
#EnableWebMvc
#Configuration
#ComponentScan("com.controller")
public class WebConfig extends WebMvcConfigurerAdapter {
#Autowired
private ServletContext servletContext;
#Bean
public LocaleResolver localeResolver(){
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setDefaultLocale(new Locale("en"));
resolver.setCookieName("locale");
resolver.setCookiePath(servletContext.getContextPath());
resolver.setCookieMaxAge(31536000);
return resolver;
}
}

Resources