【问题标题】:check if user subscription for trial period is expire or not using spring MVC检查试用期的用户订阅是否过期或未使用 Spring MVC
【发布时间】:2013-03-09 09:47:21
【问题描述】:

我正在使用 Spring MVC,想检查用户的试用期是否已过期。

我正在使用以下方法使用 Spring Security 获取用户详细信息

  public User getUserDetail() {
    Authentication auth = SecurityContextHolder.getContext()
            .getAuthentication();   
    Object principal = auth.getPrincipal();
        if(principal instanceof User){
            User user = (User) principal;               
            return user;
        }
        return null;
}

用户对象包含他第一次登录的日期。

我正在使用以下代码检查用户订阅

   UserBean userLoggedIn = (UserBean) userService.getUserDetail();

    Date dt =  userLoggedIn.getUserCreationDate();

    DateTime userCreated =  new DateTime(dt).plusDays(TRIAL_PERIOD);

    DateTime currentDateTime = new DateTime();


    if(currentDateTime.compareTo(userCreated) > 0 && userLoggedIn.getPackageType() == 0){
        return new ModelAndView("pricing","user",userLoggedIn);
    }

现在我的问题是我不想在每个控制器中重复编写上述代码。那么有什么常见的地方可以让我检查用户试用期是否到期并将他重定向到定价页面。

我有 CustomUserDetail 类,我在其中从数据库访问用户详细信息并将其放入 spring 安全会话中。所以我认为这应该是检查用户试用期是否到期的最佳地点,但我不知道如何将用户从此类重定向到定价页面。

我的 CustomUserDetail 类是

  @Service
  @Transactional(readOnly = true)
 public class CustomUserDetailsService implements UserDetailsService {

static final Logger logger = Logger.getLogger(CustomUserDetailsService.class);


@Resource(name="userService")
private UserService userService;

/* (non-Javadoc)
 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
 */
@Override
public UserDetails loadUserByUsername(String email)
        throws UsernameNotFoundException, DataAccessException {
        try {

            boolean enabled = true;
            boolean accountNonExpired = true;
            boolean credentialsNonExpired = true;
            boolean accountNonLocked = true;

            UserBean domainUser = userService.getUserByName(email);     

            domainUser.isEnabled();
            domainUser.isAccountNonExpired();
            domainUser.isCredentialsNonExpired();
            domainUser.isAccountNonLocked();


    //Collection<? extends GrantedAuthority> roles =  getAuthorities((long) domainUser.getRoleId());

    return domainUser;


    } catch (Exception e) {
        logger.error("Invalid Login.",e);
        throw new RuntimeException(e);
    }
}

---更新---

我的 spring-security.xml 是

    <form-login login-page="/login.htm" 
                authentication-failure-url="/loginfailed.htm"
                authentication-failure-handler-ref="exceptionMapper"
                default-target-url="/index.htm" 
                always-use-default-target="true"/>

    <access-denied-handler error-page="/logout.htm"/>

    <logout invalidate-session="true" 
        logout-url="/logout.htm"
        success-handler-ref="userController"/>
 <remember-me user-service-ref="customUserDetailsService" key="89dqj219dn910lsAc12" use-secure-cookie="true"  token-validity-seconds="466560000"/>
 <session-management session-authentication-strategy-ref="sas"/>
</http>

<authentication-manager>
        <authentication-provider user-service-ref="customUserDetailsService">
                <password-encoder ref="customEnocdePassword" >
                    <salt-source user-property="email"/>
                </password-encoder>
        </authentication-provider>
</authentication-manager>   
<beans:bean id="customEnocdePassword" class="com.mycom.myproj.utility.CustomEnocdePassword" />

<beans:bean id="exceptionMapper" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
<beans:property name="exceptionMappings">
    <beans:map>
        <beans:entry key="your.package.TrialPeriodExpiredException" value="/pricing"/>
    </beans:map>
</beans:property>
</beans:bean>

<beans:bean id="sas"
  class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="3" />

---更新----

现在我做的是

 <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
    <beans:property name="userDetailsService" ref="customUserDetailsService"/>
    <beans:property name="passwordEncoder" ref="customEnocdePassword"/>
    <beans:property name="preAuthenticationChecks" ref="expirationChecker"/>
</beans:bean>

<authentication-manager>
    <authentication-provider user-service-ref="authenticationProvider">
        <password-encoder ref="customEnocdePassword" >
               <salt-source user-property="email"/>
        </password-encoder>
    </authentication-provider>
</authentication-manager>

<!-- <authentication-manager>
        <authentication-provider user-service-ref="customUserDetailsService">
                <password-encoder ref="customEnocdePassword" >
                    <salt-source user-property="email"/>
                </password-encoder>
        </authentication-provider>
</authentication-manager> -->
<beans:bean id="expirationChecker" class="com.mycom.myproj.utility.UserTrialPeriodExpirationChecker" />
<beans:bean id="customEnocdePassword" class="com.mycom.myproj.utility.CustomEnocdePassword" />

现在我遇到了错误

 "Cannot convert value of type [org.springframework.security.authentication.dao.DaoAuthenticationProvider]
to required type [org.springframework.security.core.userdetails.UserDetailsService] 
for property 'userDetailsService': no matching editors or conversion strategy found"

【问题讨论】:

  • 您是否考虑过将此功能作为Filter 添加到您的过滤器链中?
  • 用户必须在定价页面上进行身份验证?如果为真,那么您可以使用自定义身份验证成功处理程序进行重定向。
  • @Nicholas ,能否请你给我一个小例子,我可以在这里使用过滤器,因为我试图使用拦截器,但这对我不起作用。
  • @Maksym 我想如果我在定价页面中使用身份验证,那么我必须再次从每个控制器调用定价页面的方法。
  • 有多种方法可以做到这一点。 @user965884,您能否为两个简单的问题提供两个答复(以便能够找到更合适的方式):1)必须在登录期间或每次请求期间检查试用期(想想您的会话多长时间,您还记得我吗?功能)? 2) 用户必须在定价页面上进行身份验证(对我来说,您可以在付款期间使用您的帐户看起来很自然)?

标签: spring spring-mvc spring-security


【解决方案1】:

您可以在DaoAuthenticationProvider 上设置自定义UserDetailsChecker,以在验证用户身份之前验证到期日期。

您的配置中的&lt;authentication-provider&gt; 元素会生成一个DaoAuthenticationProvider,但该元素上没有允许您设置其preAuthenticationChecks 属性的属性。为了解决命名空间配置的这个限制,您必须回退到将该提供程序定义为普通 bean:

<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
    <property name="userDetailsService" ref="customUserDetailsService"/>
    <property name="passwordEncoder" ref="customEnocdePassword"/>
    <property name="preAuthenticationChecks" ref="expirationChecker"/>
</bean>

并通过 &lt;authentication-manager&gt; 配置中的 id 引用它:

<security:authentication-manager>
    <security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>

上面引用的expirationChecker bean 必须实现UserDetailsChecker,这是一个接收UserDetails 对象的回调接口,如果用户的试用期已过期,您可以在其中抛出特定异常:

public class UserTrialPeriodExpirationChecker implements UserDetailsChecker {
    @Override
    public void check(UserDetails user) {
        if( /* whatever way you check expiration */ ) {
            throw new TrialPeriodExpiredException();
        }

        if (!user.isAccountNonLocked()) {
            throw new LockedException("User account is locked");
        }

        if (!user.isEnabled()) {
            throw new DisabledException("User is disabled");
        }

        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("User account has expired");
        }
    }
}

请注意,最后三个检查与过期检查无关,但您必须在此处拥有它们,因为默认实现(AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks)现在已被此类覆盖。由于默认实现是私有内部类,您不能简单地扩展它,而是需要从那里复制代码以防止锁定/禁用/等。用户登录。

一切就绪后,配置一个ExceptionMappingAuthenticationFailureHandler,将您的TrialPeriodExpiredException 映射到用户应该登陆的定价页面的URL。

<form-login authentication-failure-handler-ref="exceptionMapper" ... />

...

<bean id="exceptionMapper" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
    <property name="exceptionMappings">
        <map>
            <entry key="your.package.TrialPeriodExpiredException" value="/pricing"/>
        </map>
    </property>
</bean>

【讨论】:

  • 您好 Zagyi,能否请您也发给我在 TrialPeriodExpiredException 类中编写的代码概述。谢谢
  • 没关系,RuntimeException 的任何子类都可以。它的唯一用途是匹配exceptionMapper 中的名称。虽然从AccountStatusException 扩展可能是有意义的。您可以使用它的任何子类作为TrialPeriodExpiredException 的模板。
  • 嗨 Zagyi,它工作正常并将我重定向到定价页面,但仍然存在一个问题,即我可以使用 URL 打开其他页面,因为创建了 domainUser 对象。我必须对用户身份验证做一些事情。您能否解释一下您想告诉我的有关 AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks 的内容,因为我没有得到这部分
  • 我想说的是,通过设置你自己的PreAuthenticationChecks来验证试用期,你覆盖了默认行为,这是在提到的内部类中实现的。如果您不将那里的逻辑复制到您的自定义实现中,那么将不会执行这些检查,从而禁用/锁定/等。用户将能够进行身份验证。
  • 嗨 Zagyi,很抱歉回来晚了。我仍然不明白这个 PreAuthenticationChecks 在哪里,所以我更新了我的问题 spring-security.xml,因为我没有使用 PreAuthenticationChecks。如果可能的话,请你帮我把它放在我的 spring-security.xml 中
猜你喜欢
  • 2022-01-26
  • 2020-03-03
  • 2015-06-01
  • 2016-12-24
  • 2017-10-29
  • 2019-06-18
  • 2014-12-19
  • 2020-12-29
  • 2016-05-01
相关资源
最近更新 更多