无法使用自定义UserDetail和AuthenticationProvider的Spring安全性阻止用户多次登录

时间:2015-06-24 09:33:38

标签: java xml spring spring-mvc spring-security

我已经实现了海关UserDetail和AuthenticationProvider以使用需要多个参数的自定义基本登录表单。一切都运行正常,除了并发控制,它不会阻止用户多次登录或使用多个设备。

以下是我正在使用的配置:

的web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"> 
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener>
    <listener-class>kh.com.gfam.rsos.listener.InitializeApplicationListner</listener-class>
</listener>

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <multipart-config>
        <max-file-size>10485760</max-file-size>
        <max-request-size>104857600</max-request-size>
        <file-size-threshold>20971520</file-size-threshold>
    </multipart-config>
</servlet>
<context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>rsos.root</param-value>

<listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet>
    <description></description>
    <display-name>GetImageController</display-name>
    <servlet-name>GetImageController</servlet-name>
    <servlet-class>kh.com.gfam.rsos.presentation.controller.GetImage.GetImageController</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>GetImageController</servlet-name>
    <url-pattern>/GetImageController</url-pattern>
</servlet-mapping>
<welcome-file-list>
    <welcome-file>home</welcome-file>
</welcome-file-list>
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

弹簧security.xml文件

    <http auto-config="false" use-expressions="true"
        entry-point-ref="loginUrlAuthenticationEntryPoint">  
        <!-- omitted -->
        <intercept-url pattern="/Admin/**" access="hasRole('ROLE_ADMIN')" />
        <intercept-url pattern="/Concierge/**" access="hasRole('ROLE_USER')" />
        <intercept-url pattern="/Login" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
        <custom-filter position="FORM_LOGIN_FILTER"
            ref="companyIdUsernamePasswordAuthenticationFilter" />  
        <custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />

        <logout logout-url="/Logout" delete-cookies="true"
            invalidate-session="true" success-handler-ref="RsosLogoutSuccessHandler" />
        <csrf disabled="true" />
        <session-management invalid-session-url="/Login"
            session-authentication-strategy-ref="sas"/>

    </http>

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

    <authentication-manager alias="authenticationManager">
        <authentication-provider
            ref="companyIdUsernamePasswordAuthenticationProvider" />  
    </authentication-manager>

弹簧-config.xml中

<bean id="companyIdUsernamePasswordAuthenticationProvider"
        class="kh.com.gfam.rsos.common.security.RsosAuthenticationProvider" />

    <bean id="companyIdUsernamePasswordAuthenticationFilter"
        class="kh.com.gfam.rsos.common.security.RsosUsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="sessionAuthenticationStrategy" ref="sas" />
        <property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
        <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
        <property name="filterProcessesUrl" value="/Authenticate" />
    </bean>

    <bean id="loginUrlAuthenticationEntryPoint"
        class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <constructor-arg value="/Login" />
    </bean>

    <bean id="authenticationFailureHandler"
        class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/Login/defaultError" />
        <property name="exceptionMappings">
            <props>
                <prop
                    key="org.springframework.security.authentication.BadCredentialsException">
                    /Login/badCredentials
                </prop>
                <prop
                    key="org.springframework.security.core.userdetails.UsernameNotFoundException">
                    /Login/usernameNotFound
                </prop>
                <prop
                    key="org.springframework.security.authentication.DisabledException">
                    /Login/disabled
                </prop>
                <prop
                    key="org.springframework.security.authentication.ProviderNotFoundException">
                    /Login/providerNotFound
                </prop>
                <prop
                    key="org.springframework.security.authentication.AuthenticationServiceException">
                    /Login/authenticationService
                </prop>
            </props>
        </property>
    </bean>

    <bean id="authenticationSuccessHandler"
        class="kh.com.gfam.rsos.common.security.RsosAuthenticationSuccessHandler">
    </bean>

    <bean id="RsosLogoutSuccessHandler"
        class="kh.com.gfam.rsos.common.security.RsosLogoutSucessHandler"></bean>

    <bean id="concurrencyFilter"
        class="org.springframework.security.web.session.ConcurrentSessionFilter">
        <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
        <constructor-arg name="expiredUrl" value="/session-expired.jsp" />
    </bean>

    <bean id="sas"
        class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
        <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
        <property name="maximumSessions" value="1" />
        <property name="exceptionIfMaximumExceeded" value="true" />
    </bean>

    <bean id="sessionRegistry"
        class="org.springframework.security.core.session.SessionRegistryImpl"/>

根context.xml中

        <import resource="classpath:springConfig.xml" />
        <import resource="appServlet/servlet-context.xml" />
        <import resource="appServlet/spring-security.xml" />

        <bean id="InitializationService"
            class="kh.com.gfam.rsos.businesslogic.initialization.impl.InitializationServiceImpl">
        </bean>

自定义AuthenticationProvider类

public class RsosAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    LoginService service;

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        RsosUsernamePasswordAuthenticationToken auth = (RsosUsernamePasswordAuthenticationToken) authentication;
        String user_id = String.valueOf(auth.getName());
        String password = String.valueOf(auth.getCredentials());
        int hotel_code = auth.getHotel_code();
        int user_type = auth.getUser_type();

        UserDTO user = null;
        user = service.authenicate(hotel_code, user_id, password, user_type);

        if (user.getUser_type() == 1) {
            return new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(),
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
        } else {
            return new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(),
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN")));
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return RsosUsernamePasswordAuthenticationToken.class
                .equals(authentication);
    }
}

自定义UsernamePasswordAuthenticationToken

public class RsosUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
    private static final long serialVersionUID = 9103166337373681531L;
    private final int hotel_code;
    private final int user_type;

    public RsosUsernamePasswordAuthenticationToken(Object principal, Object credentials,
            int hotel_code, int user_type) {
        super(principal, credentials);
        this.hotel_code = hotel_code;
        this.user_type = user_type;
    }

    public RsosUsernamePasswordAuthenticationToken(Object principal, Object credentials,
            int hotel_code, int user_type,
            Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
        this.hotel_code = hotel_code;
        this.user_type = user_type;
    }

    public int getHotel_code() {
        return hotel_code;
    }

    public int getUser_type() {
        return user_type;
    }
}

Custom UsernamePasswordAuthenticationFilter

public class RsosUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {

        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported:"
                    + request.getMethod());
        }

        String username = super.obtainUsername(request);
        String password = super.obtainPassword(request);
        int hotel_code = Integer.parseInt(ObtainHotelCode(request));
        int user_type = Integer.parseInt(ObtainUserType(request));

        if (!StringUtils.hasText(hotel_code + "")) {
            throw new AuthenticationServiceException("Hotel Code is require");
        }

        RsosUsernamePasswordAuthenticationToken authrequest =
                new RsosUsernamePasswordAuthenticationToken(username, password, hotel_code, user_type); // (1)

        setDetails(request, authrequest);

        return  this.getAuthenticationManager().authenticate(authrequest);
    }

    protected String ObtainHotelCode(HttpServletRequest request) {
        return request.getParameter("hotel_code");
    }

    protected String ObtainUserType(HttpServletRequest request) {
        return request.getParameter("user_type");
    }
}

自定义AuthenticationSuccessHandler

public class RsosAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {

        UserDTO authUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication()
                .getPrincipal();
        request.getSession().setAttribute("userData", authUser);

        response.setStatus(HttpServletResponse.SC_OK);

        if (authUser.getUser_type() == 1) {
            response.sendRedirect(request.getContextPath() + "/Concierge/New_Arrival");
        } else {
            response.sendRedirect(request.getContextPath() + "/Admin/Main_Info");
        }
    }

}

自定义LogoutSucessHandler

public class RsosLogoutSucessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        if (authentication != null && authentication.getDetails() != null) {
            try {
                request.getSession().invalidate();
            } catch (Exception e) {
                e.printStackTrace();
                e = null;
            }
        }

        response.setStatus(HttpServletResponse.SC_OK);

        response.sendRedirect(request.getContextPath() + "/Login");

    }
}

自定义UserDetail类

public class UserDTO implements UserDetails {

    /**  */
    private static final long serialVersionUID = -2228367483835088451L;

    /** Hotel Code */
    private int hotel_code;
    /** UserID */
    // @Size(min=4, max=4) @Pattern(regexp = "[0-9]")
    private String user_id;
    /** User Name */
    private String user_name;
    /** Password */
    @NotNull
    @Size(min = 8, max = 30)
    private String password;
    /** User Type */
    @NotNull
    private int user_type;

    //set() .... get()//

    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getUsername() {
        return user_id;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }

    @Override
    public boolean equals(Object rhs) {
        if (rhs instanceof UserDTO) {
            return (user_id.equals(((UserDTO) rhs).user_id));
        }
        return false;
    }

    /**
     * Returns the hashcode of the {@code username}.
     */
    @Override
    public int hashCode() {
        return user_id.hashCode();
    }
}

这就是登录表单的样子

enter image description here

我是春天安全的新手,所以这里可能出现什么问题?请有人指出错误。 谢谢。

2 个答案:

答案 0 :(得分:1)

我在阅读了有关如何从Spring 3.2迁移到4.x的Spring文档之后解决了这个问题http://docs.spring.io/spring-security/site/migrate/current/3-to-4/html5/migrate-3-to-4-xml.html#m3to4-deprecations-web-cscs

我改变了

<bean id="sas"
    class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
    <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
    <property name="maximumSessions" value="1" />
    <property name="exceptionIfMaximumExceeded" value="true" />
</bean>

<bean id="sas"
    class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
    <constructor-arg>
        <list>
            <bean
                class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
                <constructor-arg ref="sessionRegistry" />
                <property name="maximumSessions" value="1" />
                <property name="exceptionIfMaximumExceeded" value="true" />
            </bean>
            <bean
                class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
            <bean
                class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
                <constructor-arg ref="sessionRegistry" />
            </bean>
        </list>
    </constructor-arg>
</bean>

对于那些使用Spring 4.x且遇到同样问题的人,你尝试阅读上面链接中的整个文档,它也可能解决你的问题。

答案 1 :(得分:0)

除了现有的会话策略之外,您似乎缺少ConcurrentSessionControlStrategy。如果你使用的是spring 3.2,你可以创建一个compoundSessionStrategy并将它们捆绑到一个策略中作为参考传递