Spring MVC - Checking if User is already logged in

2019-01-16 03:28发布

问题:

I have a Spring MVC application.It uses its own custom Login page. Upon successful login, a 'LOGGED_IN_USER' object is placed in the HTTPSession.

I want to allow only authenticated users to access URLs. I know i can achieve this by using a web filter. But, This part i want to do using Spring Security (my check will remain the same - look for 'LOGGED_IN_USER' object in HTTPSession, if present you are logged in).

My constraint is i cannot change Login behavior at present - that will not use Spring Security yet.

What aspect of Spring Security can i use to achieve this part alone - check if the request is authenticated (from logged in user)?

回答1:

There are at least 4 different ways:

spring security XML configuration

this is the easiest way

<security:http auto-config="true" use-expressions="true" ...>
   ...
  <security:intercept-url pattern="/forAll/**" access="permitAll" />
  <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>
  • @see Spring Security Reference, Chapter 16.1.1 Common Built-In Expressions
  • @see Spring Security Reference, Chapter 16.2 Web Security Expressions

Per @Secured Annotation

requires <global-method-security secured-annotations="enabled" />

@Secured("ROLE_ADMIN")
@RequestMapping(params = "onlyForAdmins")    
public ModelAndView onlyForAdmins() {
    ....
}

Per @PreAuthorize Annotation

requires <global-method-security pre-post-annotations="enabled" />

 @PreAuthorize("isAuthenticated()")
 @RequestMapping(params = "onlyForAuthenticated")
 public ModelAndView onlyForAuthenticatedUsers() {
     ....
 }

Programmatic

 SecurityContextHolder.getContext().getAuthentication() != null &&
 SecurityContextHolder.getContext().getAuthentication().isAuthenticated() &&
 //when Anonymous Authentication is enabled
 !(SecurityContextHolder.getContext().getAuthentication() 
          instanceof AnonymousAuthenticationToken) 

Custom Expression

If the built-in expressions are not enough, you can extend them. How to extend the SpEL Expressions for the method annotations is discussed for example here:

  • How to create custom methods for use in spring security expression language annotations
  • http://bmchild.blogspot.de/2012/02/creating-custom-regex-spring-security.html

But for the interceptor <security:intercept-url ... access="myCustomAuthenticatedExpression" /> there is a slightly different approach possible, that does not need to deal with the private class problem. -- I have only done it for Spring Security 3.0, but I hope it works for 3.1 too.

1.) you need to create a new class that extends from WebSecurityExpressionRoot (Prefix Web is the important part!).

public class MyCustomWebSecurityExpressionRoot
         extends WebSecurityExpressionRoot {
     public MyCustomWebSecurityExpressionRoot(Authentication a,
                 FilterInvocation f) {
          super(a, f);
     }

     /** That method is the one that does the expression evaluation! */
     public boolean myCustomAuthenticatedExpression() {
        return super.request.getSession().getValue("myFlag") != null;
     }
}

2.) you need a extend the DefaultWebSecurityExpressionRootHandler to have a handler that provides your custom expression root

 public class MyCustomWebSecurityExpressionHandler
              extends DefaultWebSecurityExpressionHandler {

      @Override        
      public EvaluationContext createEvaluationContext(Authentication a,
                FilterInvocation f) {
          StandardEvaluationContext ctx =
                   (StandardEvaluationContext) super.createEvaluationContext(a, f);

           WebSecurityExpressionRoot myRoot =
                    new MyCustomWebSecurityExpressionRoot(a, f);

           ctx.setRootObject(myRoot);
           return ctx;
      }
 }

3.) Then you need to register your handler with the voters

<security:http use-expressions="true"
 access-decision-manager-ref="httpAccessDecisionManager" ...>
      ...
    <security:intercept-url pattern="/restricted/**"
              access="myCustomAuthenticatedExpression" />         
      ...
</security:http>

<bean id="httpAccessDecisionManager"
      class="org.springframework.security.access.vote.AffirmativeBased">
    <constructor-arg name="decisionVoters">
            <list>
                <ref bean="webExpressionVoter" />
            </list>
    </constructor-arg>
</bean>

<bean id="webExpressionVoter"
      class="org.springframework.security.web.access.expression.WebExpressionVoter">
    <property name="expressionHandler"
              ref="myCustomWebSecurityExpressionHandler" />
</bean>

<bean id="myCustomWebSecurityExpressionHandler"
    class="MyCustomWebSecurityExpressionHandler" />

Spring Security 3.1 Update

Since Spring Security 3.1 it is a bit easier to implement a custom expression. One does not longer need to sublcass WebSecurityExpressionHandler and override createEvaluationContext. Instead one sublass AbstractSecurityExpressionHandler<FilterInvocation> or its subclass DefaultWebSecurityExpressionHandler and override SecurityExpressionOperations createSecurityExpressionRoot(final Authentication a, final FilterInvocation f).

 public class MyCustomWebSecurityExpressionHandler
              extends DefaultWebSecurityExpressionHandler {

      @Override        
      public SecurityExpressionOperations createSecurityExpressionRoot(
                Authentication a,
                FilterInvocation f) {
           WebSecurityExpressionRoot myRoot =
                    new MyCustomWebSecurityExpressionRoot(a, f);

           myRoot.setPermissionEvaluator(getPermissionEvaluator());
           myRoot.setTrustResolver(this.trustResolver);
           myRoot.setRoleHierarchy(getRoleHierarchy());
           return myRoot;
      }
 }


回答2:

Another solution, you can create class:

public class AuthenticationSystem {
    public static boolean isLogged() {
        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return null != authentication && !("anonymousUser").equals(authentication.getName());
    }
    // ...
    // Any another methods, for example, logout
}

Then, in controller:

@Controller
@RequestMapping(value = "/promotion")
public final class PromotionController {  
    @RequestMapping(value = {"", "/"}, method = RequestMethod.GET)
    public final String root() {
        if (!AuthenticationSystem.isLogged()) return "login"; // or some logic
        // some logic
        return "promotion/index";
    }
}

PS:

Previous solution has a problem, which explain Peter in comments.

@Controller
@RequestMapping(value = "/promotion")
public final class PromotionController {  
    @RequestMapping(value = {"", "/"}, method = RequestMethod.GET)
    public final String root(final Principal principal) {
        if (null == principal) return "login"; // or some logic
        // some logic
        return "promotion/index";
    }
}


回答3:

Is this what you're trying to achieve?

<c:choose>
  <c:when test="${pageContext.request.userPrincipal.authenticated}">Show something</c:when>
  <c:otherwise>Show something else</c:otherwise>
</c:choose>


回答4:

Many of the authentication providers will create a UserDetails object as the principal.

Another way I found - using spring-security - is to check whether the return value of Authentication.getPrincipal() is an instance of UserDetails; the method returns "anonymousUser"(String) by default.

boolean isUserLoggedIn(){
   return SecurityContextHolder.getContext().getAuthentication().getPrincipal() instanceof UserDetails
}