I am new to Spring Security and I am working on a login, logout, and session timeout feature. I have configured my code by referring to this document. My code looks below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**")
.access("hasRole('ROLE_USER')").and().formLogin()
.loginPage("/login").failureUrl("/login?error")
.usernameParameter("username")
.passwordParameter("password")
.and().logout().logoutSuccessUrl("/login?logout").and().csrf();
http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired");
}
Override the class AbstractSecurityWebApplicationInitializer
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
#Override
public boolean enableHttpSessionEventPublisher() {
return true;
}
}
I need clarification on whether I am doing it right, if it looks good, then where I need to setup the session timeout. I am doing it fully based on annotation.
If you are using JavaConfig and do not want to use XML you can create a HttpSessionListener and use getSession().setMaxInactiveInterval(), then in the Initializer add the listener in onStartup():
public class SessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
System.out.println("session created");
event.getSession().setMaxInactiveInterval(15);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
System.out.println("session destroyed");
}
}
Then in the Initializer:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new SessionListener());
}
I was able to solve above issue by adding below config in web.xml only. any better way will be accepted.
<session-config>
<session-timeout>20</session-timeout>
</session-config>
When using application.properties set property server.session.timeout= value is in seconds.
Different ways to configure session timeout time(maxInactiveInterval) in spring security.
1. By addinng session config in web.xml(from raju vaishnav's answer)
2. By creating implementation of HttpSessionListener and adding it to servlet context.(from munilvc's answer)
3. By registering your custom AuthenticationSuccessHandler in spring security configuration, and setting session maximum inactive interval in onAuthenticationSuccess method.
This implementation has advantages
On login success, You can set different value of maxInactiveInterval for different roles/users.
On login success, you can set user object in session, hence user object can be accessed in any controller from session.
Disadvantage: You can not set session timeout for ANONYMOUS user(Un-authenticated user)
Create AuthenticationSuccessHandler Handler
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException
{
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ROLE_ADMIN"))
{
request.getSession(false).setMaxInactiveInterval(60);
}
else
{
request.getSession(false).setMaxInactiveInterval(120);
}
//Your login success url goes here, currently login success url="/"
response.sendRedirect(request.getContextPath());
}
}
Register success handler
In Java Config way
#Override
protected void configure(final HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/resources/**", "/login"").permitAll()
.antMatchers("/app/admin/*").hasRole("ADMIN")
.antMatchers("/app/user/*", "/").hasAnyRole("ADMIN", "USER")
.and().exceptionHandling().accessDeniedPage("/403")
.and().formLogin()
.loginPage("/login").usernameParameter("userName")
.passwordParameter("password")
.successHandler(new MyAuthenticationSuccessHandler())
.failureUrl("/login?error=true")
.and().logout()
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.invalidateHttpSession(true)
.and().csrf().disable();
http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired=true");
}
In xml config way
<http auto-config="true" use-expressions="true" create-session="ifRequired">
<csrf disabled="true"/>
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/login" access="permitAll" />
<intercept-url pattern="/app/admin/*" access="hasRole('ROLE_ADMIN')" />
<intercept-url pattern="/" access="hasAnyRole('ROLE_USER', 'ROLE_ADMIN')" />
<intercept-url pattern="/app/user/*" access="hasAnyRole('ROLE_USER', 'ROLE_ADMIN')" />
<access-denied-handler error-page="/403" />
<form-login
login-page="/login"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/login?error=true"
username-parameter="userName"
password-parameter="password" />
<logout invalidate-session="false" success-handler-ref="customLogoutSuccessHandler"/>
<session-management invalid-session-url="/login?expired=true">
<concurrency-control max-sessions="1" />
</session-management>
</http>
<beans:bean id="authenticationSuccessHandler" class="com.pvn.mvctiles.configuration.MyAuthenticationSuccessHandler" />
Working code is available in my github repository
Working code is available in two forms
1. XML config way of implementation
2. JAVA config way of implementation
If you want to have automatic logout feature and timer which displays when session is about to expire, if user is filling form but not submitted then user can extend session by clicking on keep session alive button.
If you want to implement auto logout refer stack overflow answer on auto logout on session timeout. Hope this will help.
In your application properties use
server.servlet.session.timeout=1m (If a duration suffix is not specified, seconds will be used.)
By default it is 30 minutes.
I handled it inside subclass of UsernamePasswordAuthenticationFilter
You can get username by -
obtainUsername(request);
and apply user checks and set time out accordingly, like-
if(username.equalsIgnoreCase("komal-singh-sisodiya#xyz.com"))
{
logger.debug("setting timeout 15 min");
request.getSession(false).setMaxInactiveInterval(15*60);
}
Add the below in application.proprites
server.servlet.session.timeout=
this will work:
#EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 84600)
Related
I have following problem with 403 error handling. This is my configuration:
Part of my spring-security.xml
<sec:http auto-config="true" use-expressions="true" entry-point-ref="ep403" disable-url-rewriting="true">
<sec:form-login login-page="/login"
login-processing-url="/login"
username-parameter="email"
password-parameter="password"
/>
<sec:access-denied-handler error-page="/errors/access-denied"/>
<sec:logout invalidate-session="true" delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE"
logout-success-url="/login"/>
<sec:session-management session-fixation-protection="newSession"/>
</sec:http>
<bean id="ep403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
If logged/unauthorized user enter to link without rights to view url - he will be redirected to 403 error page and logged out from system if already logged in. I need solution which allow me to redirect logged user to 403 page & logout, but for unauthorized user i want to see login page instead of 403 error, do You know there's any chance to achieve that goal?
I try with my own AuthenticationEntryPoint implementation, I want to check user from Security Context and if its not logged in - execute different action than redirect to forbidden page, but authentication is always null in this case, because logged user on this error page is unfortunately already logged out
public class AccessDeniedEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException arg2) throws IOException, ServletException {
Authenticationauthentication = SecurityContextHolder.getContext().getAuthentication();
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
}
}
On your /errors/access-denied mapping check for JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE cookies. if they are null that means the user had not a successful login to have the cookies, so redirect him to login, if not let him see the access-denied page.
#RequestMapping(value="/errors/access-denied")
public String error(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("JSESSIONID") ||
cookie.getName().equals("SPRING_SECURITY_REMEMBER_ME_COOKIE")) {
return "access-denied";
}
}
return "login";
}
in your security config file I think you might need to add the url of you application that doesn't need or require Authentication for example .
#Override
protected void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login*",""
+ "/signin/**",""
+ "/signup/**",""
+ "/api/v1/products/**",""
+ "/api/v1/addproduct/**",""
+ "/api/v1/addCategory/**"
).permitAll()
.anyRequest().authenticated()
// .and()
// .formLogin().loginPage("/login").permitAll()
.and()
.logout();
} // #formatter:on
I am making a Spring MVC web application. I have a login page and a dashboard page. Anyone attempting to access the dashboard JSP must be logged in:
Here's my Spring Security config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity
#Import({SpringConfiguration.class})
public class SecurityContext extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
// authorizeRequests() -> use-expresions = "true"
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/createaccount","/error", "/register", "/login", "/newaccount", "/resources/**").permitAll()
.antMatchers("/**", "/*", "/").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("username")
.passwordParameter("password")
.failureUrl("/login?error=true")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.and()
.csrf();
// Upon starting the application, it prints the asdfasdf so I know the SecurityContext is loaded
System.out.println("asdfasdf");
}
// Equivalent of jdbc-user-service in XML
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("SELECT username, password, enabled FROM Users WHERE username=?")
.authoritiesByUsernameQuery("SELECT username, authority FROM authorities where username=?");
}
}
As you can see, I have some endpoints which I permit anyone to access such as /login, /register, but all other URLs require that they be authenticated. When I start the application, if I try go to the dashboard page, I can access it just fine without needing to login which is not what I want.
My issue is that I want people attempting to reach the dashboard to be sent to the login page if they are not logged in/authenticated.
I'm trying to avoid using XML entirely and only use Java to configure my application, would anyone know what I'm doing wrong? I am almost certain it's something wrong with with my SecurityContext.
I might as well include the context XML too of which I'm trying to convert to Java config style
<security:authentication-manager>
<security:jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="select username, password, enabled from Users where username=?"
authorities-by-username-query="select username, authority from Authority where username =? " />
</security:authentication-provider>
</security:authentication-manager>
<security:http use-expressions="true">
<security:intercept-url pattern="/newaccount"
access="permitAll" />
<security:intercept-url pattern="/accountcreated"
access="permitAll" />
<security:intercept-url pattern="/createaccount"
access="permitAll" />
<security:intercept-url pattern="/error"
access="permitAll" />
<security:intercept-url pattern="/resources/**"
access="permitAll" />
<security:intercept-url pattern="/login"
access="permitAll" />
<security:intercept-url pattern="/setemote"
access="isAuthenticated()" />
<security:intercept-url pattern="/**"
access="isAuthenticated()" />
<security:intercept-url pattern="/*"
access="isAuthenticated()" />
<security:form-login login-page="/login"
default-target-url="/" login-processing-url="/j_spring_security_check"
username-parameter="username" password-parameter="password"
authentication-failure-url="/login?error=true" />
<security:csrf />
</security:http>
Good day.
You have to be sure that you have SecurityWebApplicationInitializer, looking like that:
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(SecurityContext.class);
}
}
Where SecurityContext - is your class extending WebSecurityConfigurerAdapter.
If you already have it then the problem might be in the lack of roles.
To have roles you might want to implement the config a bit differently, something like that:
.antMatchers("/restricted_area/*")
.access("hasRole('ADMIN')")
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.permitAll()
.and()
.logout()
.permitAll();
For working with roles and authentication you can extend org.springframework.security.core.userdetails.UserDetailsService having a separate class that would work along with the Spring' authorization/authentication machinery checking the credentials.
As you see I also have authenticationSuccessHandler here. This is actually extended
org.springframework.security.web.authentication.AuthenticationSuccessHandler
What it does is redirecting to specific pages depending on the role: e.g. regular user to user' dashboard, admin to admin' dashboard.
Not sure if this is relevant to your question though, but the implementation is something like that:
#Component("customHandler")
public class CustomAuthenticationHandler implements AuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationHandler.class);
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
Object principal = authentication.getPrincipal();
String username = ((UserDetails) principal).getUsername();
userService.updateLastLoginTimeByName(username);
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
/**
* Builds the target URL according to the logic defined in the main class
* Javadoc.
*/
protected String determineTargetUrl(Authentication authentication) {
boolean isAdmin = false;
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isAdmin = true;
break;
}
}
if (isAdmin) {
return "/restricted_area/";
} else {
throw new IllegalStateException();
}
}
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
protected RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
}
this is my spring-security.xml:
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/courses*" access="hasRole('ROLE_USER')" />
<custom-filter before="FORM_LOGIN_FILTER" ref="MyAuthFilter" />
<form-login
login-page="/login"
default-target-url="/courses"
authentication-failure-url="/login?error"
username-parameter="loginField"
password-parameter="passwordField" />
<csrf disabled="true" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="ars" password="1234" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
here is MyAuthFilter:
#Component("MyAuthFilter")
public class MyAuthFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
#Qualifier("authenticationManager")
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
System.out.println("running my own version of UsernmePasswordFilter ... ");
LoginForm loginForm = new LoginForm();
loginForm.setUsername(request.getParameter("login"));
loginForm.setPassword(request.getParameter("password"));
request.setAttribute("error", 3);
System.out.println("login : " + loginForm.getUsername());
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginForm.getUsername(), loginForm.getPassword());
setDetails(request, authRequest);
Authentication authResult = this.getAuthenticationManager().authenticate(authRequest);
return authResult;
}
}
When i enter wrong login or password it shows "bad credentials" error instead of redirecting to a login page.Without custom filter it works fine.
I just want to check what wrong with login\password and set "error" wariable, wich i use in login form to show concrete error like " empty pass" etc.
I need to make a login page witch shows concrete error like "empty pass\empty login\ both empty\wrong login or pass". I will be very greatfull if someone could share a link with example or guide for those validation.
Define success and failure handler
#Bean
public AuthenticationSuccessHandler getSuccessHandler(){
SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
handler.setDefaultTargetUrl("/login.html");
return handler;
}
#Bean
public AuthenticationFailureHandler getFailureHandler(){
SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler();
handler.setDefaultFailureUrl("/login.html");
return handler;
}
in your filter
#Autowired
#Qualifier("authenticationManager")
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) {
super.setAuthenticationManager(authenticationManager);
this.setAuthenticationSuccessHandler(successHandler);
this.setAuthenticationFailureHandler(failureHandler);
}
I have working XML-based security configuration in my Spring MVC project:
<security:http use-expressions="true"
authentication-manager-ref="authenticationManager">
<security:intercept-url pattern="/" access="permitAll"/>
<security:intercept-url pattern="/dashboard/home/**" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')"/>
<security:intercept-url pattern="/dashboard/users/**" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/rest/users/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login login-page="/"/>
</security:http>
And I have question: is it possible to fully replace it by Java configuration? What annotations and where should I use for "use-expressions", "intercept-url", etc.?
Yes, if you are using Spring security 3.2 and above, it will be something like this :
#Configuration
#EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/dashboard/home/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/dashboard/users/**").hasRole("ADMIN")
.antMatchers("/rest/users/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.permitAll();
}
// Possibly more overridden methods ...
}
Here is my spring security config:
<http pattern="/auth/login" security="none" />
<http pattern="/auth/loginFailed" security="none" />
<http pattern="/resources/**" security="none" />
<http auto-config="true" access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/auth/logout" access="permitAll"/>
<intercept-url pattern="/admin/**" access="ADMINISTRATIVE_ACCESS"/>
<intercept-url pattern="/**" access="XYZ_ACCESS"/>
<form-login
login-page="/auth/login"
authentication-failure-url="/auth/loginFailed"
authentication-success-handler-ref="authenticationSuccessHandler" />
<logout logout-url="/auth/logout" logout-success-url="/auth/login" />
</http>
The authenticationSuccessHandler extends the SavedRequestAwareAuthenticationSuccessHandler ensuring that the user is redirected to the page he originally requested.
However, since /auth/login is marked as security="none", I am unable to successfully redirect the user to the homepage if he accesses the login page after being logged in. I believe this is the right user experience too.
I tried the below too but the Principal object is always null, presumably because of the security="none" attribute again.
#RequestMapping(value = "/auth/login", method = GET)
public String showLoginForm(HttpServletRequest request, Principal principal) {
if(principal != null) {
return "redirect:/";
}
return "login";
}
I've checked the topic more deeply than last time and found that you have to determine if user is authenticated by yourself in controller. Row Winch (Spring Security dev) says here:
Spring Security is not aware of the internals of your application
(i.e. if you want to make your login page flex based upon if the user
is logged in or not). To show your home page when the login page is
requested and the user is logged in use the SecurityContextHolder in
the login page (or its controller) and redirect or forward the user to
the home page.
So solution would be determining if user requesting /auth/login is anonymous or not, something like below.
applicationContext-security.xml:
<http auto-config="true" use-expressions="true"
access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/auth/login" access="permitAll" />
<intercept-url pattern="/auth/logout" access="permitAll" />
<intercept-url pattern="/admin/**" access="ADMINISTRATIVE_ACCESS" />
<intercept-url pattern="/**" access="XYZ_ACCESS" />
<form-login login-page="/auth/login"
authentication-failure-url="/auth/loginFailed"
authentication-success-handler-ref="authenticationSuccessHandler" />
<logout logout-url="/auth/logout" logout-success-url="/auth/login" />
</http>
<beans:bean id="defaultTargetUrl" class="java.lang.String">
<beans:constructor-arg value="/content" />
</beans:bean>
<beans:bean id="authenticationTrustResolver"
class="org.springframework.security.authentication.AuthenticationTrustResolverImpl" />
<beans:bean id="authenticationSuccessHandler"
class="com.example.spring.security.MyAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" ref="defaultTargetUrl" />
</beans:bean>
Add to applicationContext.xml bean definition:
<bean id="securityContextAccessor"
class="com.example.spring.security.SecurityContextAccessorImpl" />
which is class
public final class SecurityContextAccessorImpl
implements SecurityContextAccessor {
#Autowired
private AuthenticationTrustResolver authenticationTrustResolver;
#Override
public boolean isCurrentAuthenticationAnonymous() {
final Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
return authenticationTrustResolver.isAnonymous(authentication);
}
}
implementing simple interface
public interface SecurityContextAccessor {
boolean isCurrentAuthenticationAnonymous();
}
(SecurityContextHolder accessing code is decoupled from controller, I followed suggestion from this answer, hence SecurityContextAccessor interface.)
And last but not least redirect logic in controller:
#Controller
#RequestMapping("/auth")
public class AuthController {
#Autowired
SecurityContextAccessor securityContextAccessor;
#Autowired
#Qualifier("defaultTargetUrl")
private String defaultTargetUrl;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
if (securityContextAccessor.isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:" + defaultTargetUrl;
}
}
}
Defining defaultTargetUrl String bean seems like a hack, but I don't have better way not to hardcode url... (Actually in our project we use <util:constant> with class containing static final String fields.) But it works after all.
You could also restrict your login page to ROLE_ANONYMOUS and set an <access-denied-handler />:
<access-denied-handler ref="accessDeniedHandler" />
<intercept-url pattern="/auth/login" access="ROLE_ANONYMOUS" />
And in your handler check if the user is already authenticated:
#Service
public class AccessDeniedHandler extends AccessDeniedHandlerImpl {
private final String HOME_PAGE = "/index.html";
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && !(auth instanceof AnonymousAuthenticationToken)) {
response.sendRedirect(HOME_PAGE);
}
super.handle(request, response, e);
}
}
Implement a Redirect Interceptor for this purpose:
The Interceptor (implementing HandlerInterceptor interface) check if someone try to access the login page, and if this person is already logged in, then the interceptor sends a redirect to the index page.
public class LoginPageRedirectInterceptor extends HandlerInterceptorAdapter {
private String[] loginPagePrefixes = new String[] { "/login" };
private String redirectUrl = "/index.html";
private UrlPathHelper urlPathHelper = new UrlPathHelper();
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (isInLoginPaths(this.urlPathHelper.getLookupPathForRequest(request))
&& isAuthenticated()) {
response.setContentType("text/plain");
sendRedirect(request, response);
return false;
} else {
return true;
}
}
private boolean isAuthenticated() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return false;
}
if (authentication instanceof AnonymousAuthenticationToken) {
return false;
}
return authentication.isAuthenticated();
}
private void sendRedirect(HttpServletRequest request,
HttpServletResponse response) {
String encodedRedirectURL = response.encodeRedirectURL(
request.getContextPath() + this.redirectUrl);
response.setStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
response.setHeader("Location", encodedRedirectURL);
}
private boolean isInLoginPaths(final String requestUrl) {
for (String login : this.loginPagePrefixes) {
if (requestUrl.startsWith(login)) {
return true;
}
}
return false;
}
}
You can keep it simple flow by access-denied-page attribute in http element or as dtrunk said to write handler for access denied as well as. the config would be like
<http access-denied-page="/403" ... >
<intercept-url pattern="/login" access="ROLE_ANONYMOUS" />
<intercept-url pattern="/user/**" access="ROLE_USER" />
<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
<form-login login-page="/login" default-target-url="/home" ... />
...
</http>
in controller for /403
#RequestMapping(value = "/403", method = RequestMethod.GET)
public String accessDenied() { //simple impl
return "redirect:/home";
}
and for /home
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Authentication authentication) {
// map as many home urls with Role
Map<String, String> dashBoardUrls = new HashMap<String, String>();
dashBoardUrls.put("ROLE_USER", "/user/dashboard");
dashBoardUrls.put("ROLE_ADMIN", "/admin/dashboard");
String url = null;
Collection<? extends GrantedAuthority> grants = authentication
.getAuthorities();
// for one role per user
for (GrantedAuthority grantedAuthority : grants) {
url = dashBoardUrls.get(grantedAuthority.getAuthority());
}
if (url == null)
return "/errors/default_access_denied.jsp";
return "redirect:" + url;
}
and when you make request for /admin/dashboard without logged in, it will redirect /login automatically by security
<http pattern="/login" auto-config="true" disable-url-rewriting="true">
<intercept-url pattern="/login" access="ROLE_ANONYMOUS"/>
<access-denied-handler error-page="/index.jsp"/>
</http>
You can try checking
if(SecurityContextHolder.getContext().getAuthentication() == null)
True means the user isn't authenticated, and thus can be sent to the login page. I don't know how robust/reliable this is, but it seems reasonable to try.