Spring security - Restricting Authenticated User redirection to Login - spring

After login, when login url is accessed with out logging out, login page is shown, but I do not want the login Page, instead remain on the same page even when login url is accessed from address bar.
Following is my security configuration:
<form-login login-page="/loginform.do" authentication-failure-url = "/loginform.do?error=1" default-target-url="/dashBoard.do" always-use-default- target="false" />
One solution I come across is to redirect page, if the role is not 'ROLE_ANONYMOUS'
<sec:authorize ifNotGranted="ROLE_ANONYMOUS">
<% response.sendRedirect("/mainpage.jsp"); %>
</sec:authorize>
But can a similar configuration be done in security configuration file ?

I solved this with an HandlerInterceptor because I dont know a build in solution.
import org.springframework.web.util.UrlPathHelper;
...
public class PreventLoginPageForLoggedInUserInterceptor extends HandlerInterceptorAdapter {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
if (urlPathHelper.getLookupPathForRequest(request).startsWith("/login"))
&& isAuthenticated()) {
sendRedirect(request, response);
return false;
} else {
return true;
}
}
private void sendRedirect(HttpServletRequest request,
HttpServletResponse response) {
response.setStatus(HttpStatus.TEMPORARY_REDIRECT.value());
response.setHeader("Location", response.encodeRedirectURL(request.getContextPath() + "/"));
}
private boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (authentication != null)
&& (!authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()
}
}

Related

Spring Security: Unable to re-login

I am fixing an existing application, where first login works fine and the logout works fine taking me back to login page.
However after logout, when I try to re-login without browser refresh (just enter credentials on login page), I am getting redirected to Host based URL(which is blocked over the intranet).
Reason
SavedRequest savedRequest = requestCache.getRequest(request, response);
is null during re-login.
I think that may be because I loggedOut, session is cleared by LogoutHandler.
Code for reference.
`public class BroadleafAdminAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
RequestCache requestCache = new HttpSessionRequestCache();
private static final String successUrlParameter = "successUrl=";
private static final String APPLICATIONURL = "APPLICATIONURL";
#Resource(name = "blAdminSecurityRemoteService")
protected SecurityVerifier adminRemoteSecurityService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
AdminUser user = adminRemoteSecurityService.getPersistentAdminUser();
if (user != null && user.getLastUsedSandBoxId() != null) {
request.getSession(false).setAttribute(BroadleafSandBoxResolver.SANDBOX_ID_VAR, user.getLastUsedSandBoxId());
}
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
.
String targetUrl = savedRequest.getRedirectUrl();
.
*Logic where I update my targetUrl*
.
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}`
I want to avoid going into this condition
if (savedRequest == null) Since I am then unable to execute
*Logic where I update my targetUrl*

Spring Security 5 Stateless OAuth2 Login - how to implement cookies based AuthorizationRequestRepository

I'm trying to have Google/Facebook login using Spring Security 5 OAuth2 login feature. But the problem I'm facing is that I'm coding a stateless API, whereas Spring security 5 uses HttpSessionOAuth2AuthorizationRequestRepository to store authorization requests, which uses session. So, I thought not to use that and code a cookie based implementation, which looks as below:
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
private static final String COOKIE_NAME = "some-name";
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
return fetchCookie(request)
.map(this::toOAuth2AuthorizationRequest)
.orElse(null);
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request,
HttpServletResponse response) {
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
if (authorizationRequest == null) {
deleteCookie(request, response);
return;
}
Cookie cookie = new Cookie(COOKIE_NAME, fromAuthorizationRequest(authorizationRequest));
cookie.setPath("/");
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
private String fromAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) {
return Base64.getUrlEncoder().encodeToString(
SerializationUtils.serialize(authorizationRequest));
}
private void deleteCookie(HttpServletRequest request, HttpServletResponse response) {
fetchCookie(request).ifPresent(cookie -> {
cookie.setValue("");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
});
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
// Question: How to remove the cookie, because we don't have access to response object here.
return loadAuthorizationRequest(request);
}
private Optional<Cookie> fetchCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0)
for (int i = 0; i < cookies.length; i++)
if (cookies[i].getName().equals(COOKIE_NAME))
return Optional.of(cookies[i]);
return Optional.empty();
}
private OAuth2AuthorizationRequest toOAuth2AuthorizationRequest(Cookie cookie) {
return SerializationUtils.deserialize(
Base64.getUrlDecoder().decode(cookie.getValue()));
}
}
The above basically stores the data in a cookie instead of session. I've a couple of questions:
How exactly to code the removeAuthorizationRequest method above? I wanted the cookie removed there, but we don't have access to the response object.
Does the above (cookie based) approach look okay? E.g. any security issues?
Update: Have created an issue at https://github.com/spring-projects/spring-security/issues/5313 . Until that's addressed, here is a workaround I came up with: https://www.naturalprogrammer.com/blog/1681261/spring-security-5-oauth2-login-signup-stateless-restful-web-services

How to know that a session is expired?

I set values to the session object in the method of a controller after success of login :
#RequestMapping(value = "/", method = RequestMethod.POST)
public ModelAndView processLogin(Model model, HttpServletRequest request, HttpSession session, #RequestParam String login, #RequestParam String pwd) {
if ( utilisateurDao.verifierLoginUser(login) ) {
if ( utilisateurDao.verifierUser(login, pwd) ) {
HashMap<String, String> criteres = new HashMap<String, String>();
criteres.put("user_login", login);
criteres.put("user_passwd", pwd);
List<Utilisateur> users = utilisateurDao.lireParCritere(criteres);
session.setAttribute("user_code", ((Utilisateur)users.get(0)).getUser_code());
session.setAttribute("menu", menuDao.afficherMenuParUtilisateur((Integer)session.getAttribute("user_code"), env, request, session));
criteres.clear();
users.clear();
criteres.put("user_code", String.valueOf(session.getAttribute("user_code")));
users = utilisateurDao.lireParCritere(criteres);
session.setAttribute("user_names", ((Utilisateur)users.get(0)).getNoms());
session.setAttribute("logout_menu", env.getProperty("menu.logout"));
return new ModelAndView("redirect:/accueil");
} else {
ModelAndView modelViewLogin = new ModelAndView("redirect:/");
modelViewLogin.addObject("e", "p").addObject("l", login);
return modelViewLogin;
}
} else {
ModelAndView modelViewLogin = new ModelAndView("redirect:/");
modelViewLogin.addObject("e", "l");
return modelViewLogin;
}
}
Then I opened the app inactive for some minutes. After that I went to the "accueil" path. Then the menu was not shown anymore ! The menu was got from session. So how to know that the session is expired and where is the convenient place to test it ?
By default in spring security session is stored in SessionRegistry.
By using SecurityContext you can get this info in your controller code.
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
If you want to be notified when session has expired or person logged out you can always register listener on SessionDestroyedEvent- documentation.
example:
#Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
//do your stuff here
}
}
Its also worth to refer to spring docs for that subject.
You can make a Interceptor,
#Component
public class RequestInterceptor extends HandlerInterceptorAdapter
In this interceptor you can control the HttpServletRequest
and check if obj exists into them and then you can throw to a new SessionExpiredException and catch with #ExceptionMapper (https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc)
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if (request.getSession().getAttribute("user")==null) {
throw new SessionExpiredException();
}
return true;
}
I check like below. I think it might be help.
public boolean isUserLoggedIn(HttpServletRequest request) throws IOException {
SecurityContext securityContext = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
if(securityContext != null) {
Authentication authentication = securityContext.getAuthentication();
if(null != authentication && authentication.isAuthenticated() != true)
return false;
else
return true;
} else {
return false;
}
}

Spring security authentication based on request parameter

The application I'm working on already has Spring Security to handle form based authentication. Now the requirement is to login a user programmatically via an external service if a token is found in one of the request parameters.
In other words, if a particular request parameter, say "token", exists, it needs to call an external service with that token to verify if it's a valid token. If it is then the user will be logged in.
I can't figure out how and where to "trigger" or "hook on to" Spring Security to check this parameter and make the verification then authenticate the user when appropriate since there is no login form. I thought there should be something in Spring Security that can be extended or customized to do this?
I looked through Spring Security documentation and wonder if AbstractPreAuthenticatedProcessingFilter is the right thing to start with?
I have a similar setup in my application. Here are the basic elements as far as I can tell:
You need to create an AuthenticationProvider like so:
public class TokenAuthenticationProvider implements AuthenticationProvider {
#Autowired private SomeService userSvc;
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.isAuthenticated())
return auth;
String token = auth.getCredentials().toString();
User user = userSvc.validateApiAuthenticationToken(token);
if (user != null) {
auth = new PreAuthenticatedAuthenticationToken(user, token);
auth.setAuthenticated(true);
logger.debug("Token authentication. Token: " + token + "; user: " + user.getDisplayName());
} else
throw new BadCredentialsException("Invalid token " + token);
return auth;
}
}
You also need to create a Filter to turn the custom parameter into an authentication token:
public class AuthenticationTokenFilter implements Filter {
#Override
public void init(FilterConfig fc) throws ServletException {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null && context.getAuthentication().isAuthenticated()) {
// do nothing
} else {
Map<String,String[]> params = req.getParameterMap();
if (!params.isEmpty() && params.containsKey("auth_token")) {
String token = params.get("auth_token")[0];
if (token != null) {
Authentication auth = new TokenAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
fc.doFilter(req, res);
}
#Override
public void destroy() {
}
class TokenAuthentication implements Authentication {
private String token;
private TokenAuthentication(String token) {
this.token = token;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new ArrayList<GrantedAuthority>(0);
}
#Override
public Object getCredentials() {
return token;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return null;
}
#Override
public boolean isAuthenticated() {
return false;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
// your custom logic here
}
}
}
You need to create beans for these:
<beans:bean id="authTokenFilter" class="com.example.security.AuthenticationTokenFilter" scope="singleton" />
<beans:bean id="tokenAuthProvider" class="com.example.security.TokenAuthenticationProvider" />
Finally, you need to wire these beans into your security config (adjust accordingly):
<sec:http >
<!-- other configs here -->
<sec:custom-filter ref="authTokenFilter" after="BASIC_AUTH_FILTER" /> <!-- or other appropriate filter -->
</sec:http>
<sec:authentication-manager>
<!-- other configs here -->
<sec:authentication-provider ref="tokenAuthProvider" />
</sec:authentication-manager>
There might be another way, but this definitely works (using Spring Security 3.1 at the moment).
If you use Spring MVC controller or service, where targe request parameter is passed, then you can use #PreAuthorize Spring security annotation.
Say, you have some Spring service that can check passed token and perform authentication if passed token is valid one:
#Service("authenticator")
class Authenticator {
...
public boolean checkTokenAndAuthenticate(Object token) {
...
//check token and if it is invalid return "false"
...
//if token is valid then perform programmatically authentication and return "true"
}
...
}
Then, with Spring security #PreAuthorize annotation you can do this it next way:
...
#PreAuthorize("#authenticator.checkTokenAndAuthenticate(#token)")
public Object methodToBeChecked(Object token) { ... }
...
Also, you should enable Spring security annotations by and add spring-security-aspects to POM (or jar to classpath).

Spring Security authentication via URL

I have a Spring MVC app that uses Spring Security and form based login for authorization/authentication.
Now I want to add a special URL that includes a token that should be accessible without additional information because the token is unique to a user:
http://myserver.com/special/5f6be0c0-87d7-11e2-9e96-0800200c9a66/text.pdf
How do I need to configure Spring Security to use that token for user authentication?
You need to define your custom pre auth filter.
In security app context within http tag:
<custom-filter position="PRE_AUTH_FILTER" ref="preAuthTokenFilter" />
Then define your filter bean (and its properties approprietly):
<beans:bean class="com.yourcompany.PreAuthTokenFilter"
id="preAuthTokenFilter">
<beans:property name="authenticationDetailsSource" ref="authenticationDetailsSource" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</beans:bean>
Create your custom filter extended from GenericFilterBean
public class PreAuthTokenFilter extends GenericFilterBean {
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
#Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String token = getTokenFromHeader(request);//your method
if (StringUtils.isNotEmpty(token)) {
/* get user entity from DB by token, retrieve its username and password*/
if (isUserTokenValid(/* some args */)) {
try {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (AuthenticationException e) {
}
}
}
chain.doFilter(request, response);
}
/*
other methods
*/
If you don't want or cannot retrieve a password, you need to create your own AbstractAuthenticationToken which will receive only username as param (principal) and use it instead of UsernamePasswordAuthenticationToken:
public class PreAuthToken extends AbstractAuthenticationToken {
private final Object principal;
public PreAuthToken(Object principal) {
super(null);
super.setAuthenticated(true);
this.principal = principal;
}
#Override
public Object getCredentials() {
return "";
}
#Override
public Object getPrincipal() {
return principal;
}
}
You can provide a custom PreAuthenticatedProcessingFilter and PreAuthenticatedAuthenticationProvider. See Pre-Authentication Scenarios chapter for details.
I ran into this problem, and solved it using a custom implementation of the Spring Security RembereMe Service infrastructure. Here is what you need to do.
Define your own Authentication object
public class LinkAuthentication extends AbstractAuthenticationToken
{
#Override
public Object getCredentials()
{
return null;
}
#Override
public Object getPrincipal()
{
return the prncipal that that is passed in via the constructor
}
}
Define
public class LinkRememberMeService implements RememberMeServices, LogoutHandler
{
/**
* It might appear that once this method is called and returns an authentication object, that authentication should be finished and the
* request should proceed. However, spring security does not work that way.
*
* Once this method returns a non null authentication object, spring security still wants to run it through its authentication provider
* which, is totally brain dead on the part of Spring this, is why there is also a
* LinkAuthenticationProvider
*
*/
#Override
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response)
{
String accessUrl = ServletUtils.getApplicationUrl(request, "/special/");
String requestUrl = request.getRequestURL().toString();
if (requestUrl.startsWith(accessUrl))
{
// take appart the url extract the token, find the user details object
// and return it.
LinkAuthentication linkAuthentication = new LinkAuthentication(userDetailsInstance);
return linkAuthentication;
} else
{
return null;
}
}
#Override
public void loginFail(HttpServletRequest request, HttpServletResponse response)
{
}
#Override
public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication)
{
}
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
{
}
}
public class LinkAuthenticationProvider implements AuthenticationProvider
{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
// Spring Security is totally brain dead and over engineered
return authentication;
}
#Override
public boolean supports(Class<?> authentication)
{
return LinkAuthentication.class.isAssignableFrom(authentication);
}
}
Hack up the rest rest of your spring security xml to define a custom authentication provider, and the custom remember me service.
P.S. if you do base64 encoding of the GUID in your URL it will be a few characters shorter. You can use the Apache commons codec base64 binary encoder / decoder to do safer url links.
public static String toBase64Url(UUID uuid)
{
return Base64.encodeBase64URLSafeString(toBytes(uuid));
}

Resources