1. 程式人生 > >Spring Security 自定義登入驗證與自定義回撥地址

Spring Security 自定義登入驗證與自定義回撥地址

1 配置檔案 security-ns.xml
   
     <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    //需要過濾不被攔截的請求
    <security:http pattern="/openapi/**" security="none" />
    <security:http pattern="/useraccounts/userprofile.json" security="none" />
    <security:http pattern="/useraccounts/register**" security="none" />
   
     //entry-point-ref 配置自定義登入
    <security:http auto-config="false" entry-point-ref="authenticationEntryPoint">
        <security:intercept-url pattern="/backManage/**" access="ROLE_BACK_USER" />
        <security:intercept-url pattern="/mall/**"       access="ROLE_BACK_USER" />
        <security:intercept-url pattern="/thirdUser/**"  access="ROLE_USER" />
        <security:intercept-url pattern="/useraccounts/**" access="ROLE_USER" />
        <security:intercept-url pattern="/cart/**.html" access="ROLE_USER" />
        <security:intercept-url pattern="/ticket/**" access="ROLE_USER,ROLE_BACK_USER" />
        <security:intercept-url pattern="/order/**" access="ROLE_USER" />
        <security:intercept-url pattern="/comment/**" access="ROLE_USER" />
        <security:intercept-url pattern="/personal/**" access="ROLE_USER" />
        <security:intercept-url pattern="/favorite/**" access="ROLE_USER" />
    
        //需要替換的Filter順序,配置自定義custom-filter時必須蔣auto-config="false",不然會報已經存在同樣的過濾器錯誤
        <security:custom-filter ref="myLoginFilter"  position="FORM_LOGIN_FILTER" />
        //登出配置
        <security:logout logout-success-url="${local.service.url}"/>
    </security:http>

     //密碼加密工具類
    <bean id="encoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/>
    //認證管理器
    <security:authentication-manager alias="authenticationManager">
        //UserDetailsService實現 主要用於使用者的查詢
        <security:authentication-provider user-service-ref="userLoginService">
            <security:password-encoder  ref="encoder">
            </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>

    <bean id="myLoginFilter" class="com.sale114.www.sercurity.MyUsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureHandler" ref="failureHandler"/>
        <property name="authenticationSuccessHandler" ref="successHandler"/>
    </bean>

    //成功登入後
    <bean id="successHandler" class="com.sale114.www.sercurity.MySavedRequestAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="${local.service.url}"/>
    </bean>
    //登入失敗
    <bean id="failureHandler" class="com.sale114.www.sercurity.MySimpleUrlAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="${local.service.url}/login.html?validated=false"/>
    </bean>
    
    <bean id="authenticationEntryPoint"
        class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <property name="loginFormUrl" value="${local.service.url}/login.html" />
    </bean>
</beans>


2 UserLoginServiceImpl 查詢使用者實現類

     @Named("userLoginService")
public class UserLoginServiceImpl  implements UserDetailsService ,LoginService{

    @Inject
    private UserLoginDAO userLoginDAO;
    
    @Override
    public WrappedUserLogin getUserLogin() {
        try {
            WrappedUserLogin wrappedUserLogin = (WrappedUserLogin) SecurityContextHolder
                    .getContext().getAuthentication().getPrincipal();
            return wrappedUserLogin;
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        System.out.println("使用者名稱-------------"+username);
        UserLogin userLogin =  null;
        if(username != null && !"".equals(username)&& username.indexOf("@") > 0){
              userLogin = userLoginDAO.findByEmail(username);
              username = userLogin.getNick();
        }else{
            userLogin = userLoginDAO.findByNick(username);
        }
        System.out.println("user is null ---"+userLogin.getUserType());
        String nick = userLogin.getNick();
        String email = userLogin.getEmail();
        String mobile = userLogin.getMobile();
        int userType = userLogin.getUserType();
        List<GrantedAuthority> resultAuths = new ArrayList<GrantedAuthority>();
        

        // 前臺使用者
        if (userType == 1) {
            resultAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
        } else {
            resultAuths.add(new SimpleGrantedAuthority("ROLE_BACK_USER"));
        }
        
        return new WrappedUserLogin(userLogin.getId(), email, nick, mobile, userLogin.getPassword(), userType,resultAuths);
    }

}

3 重寫使用者名稱密碼驗證
     public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
        //使用者名稱
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
        //密碼
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";
        //需要回調的URL 自定義引數
        public static final String SPRING_SECURITY_FORM_REDERICT_KEY = "spring-security-redirect";
        
        /**
         * @deprecated If you want to retain the username, cache it in a customized {@code AuthenticationFailureHandler}
         */
        @Deprecated
        public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";

        private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
        private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
        private String redirectParameter = SPRING_SECURITY_FORM_REDERICT_KEY;
        private boolean postOnly = true;

        //~ Constructors ===================================================================================================

        public MyUsernamePasswordAuthenticationFilter() {
           super();
        }

        //~ Methods ========================================================================================================

        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
            String username = obtainUsername(request);
            String password = obtainPassword(request);
            String redirectUrl = obtainRedercitUrl(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }
            //自定義回撥URL,若存在則放入Session
            if(redirectUrl != null && !"".equals(redirectUrl)){
                request.getSession().setAttribute("callCustomRediretUrl", redirectUrl);
            }
            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }

        /**
         * Enables subclasses to override the composition of the password, such as by including additional values
         * and a separator.<p>This might be used for example if a postcode/zipcode was required in addition to the
         * password. A delimiter such as a pipe (|) should be used to separate the password and extended value(s). The
         * <code>AuthenticationDao</code> will need to generate the expected password in a corresponding manner.</p>
         *
         * @param request so that request attributes can be retrieved
         *
         * @return the password that will be presented in the <code>Authentication</code> request token to the
         *         <code>AuthenticationManager</code>
         */
        protected String obtainPassword(HttpServletRequest request) {
            return request.getParameter(passwordParameter);
        }

        /**
         * Enables subclasses to override the composition of the username, such as by including additional values
         * and a separator.
         *
         * @param request so that request attributes can be retrieved
         *
         * @return the username that will be presented in the <code>Authentication</code> request token to the
         *         <code>AuthenticationManager</code>
         */
        protected String obtainUsername(HttpServletRequest request) {
            return request.getParameter(usernameParameter);
        }
        
        
        protected String obtainRedercitUrl(HttpServletRequest request) {
            return request.getParameter(redirectParameter);
        }

        /**
         * Provided so that subclasses may configure what is put into the authentication request's details
         * property.
         *
         * @param request that an authentication request is being created for
         * @param authRequest the authentication request object that should have its details set
         */
        protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        }

        /**
         * Sets the parameter name which will be used to obtain the username from the login request.
         *
         * @param usernameParameter the parameter name. Defaults to "j_username".
         */
        public void setUsernameParameter(String usernameParameter) {
            Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
            this.usernameParameter = usernameParameter;
        }

        /**
         * Sets the parameter name which will be used to obtain the password from the login request..
         *
         * @param passwordParameter the parameter name. Defaults to "j_password".
         */
        public void setPasswordParameter(String passwordParameter) {
            Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
            this.passwordParameter = passwordParameter;
        }

        /**
         * Defines whether only HTTP POST requests will be allowed by this filter.
         * If set to true, and an authentication request is received which is not a POST request, an exception will
         * be raised immediately and authentication will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
         * will be called as if handling a failed authentication.
         * <p>
         * Defaults to <tt>true</tt> but may be overridden by subclasses.
         */
        public void setPostOnly(boolean postOnly) {
            this.postOnly = postOnly;
        }

    
}



4 SimpleUrlAuthenticationSuccessHandler重寫
     public class MySavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
     @Value(value = "${local.service.url}")
     private String LOCAL_SERVER_URL;
    
     protected final Log logger = LogFactory.getLog(this.getClass());

        private RequestCache requestCache = new HttpSessionRequestCache();

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws ServletException, IOException {
            SavedRequest savedRequest = requestCache.getRequest(request, response);
            if (savedRequest == null) {
                System.out.println("savedRequest is null ");
                //使用者判斷是否要使用上次通過session裡快取的回撥URL地址
                int flag = 0;
                //通過提交登入請求傳遞需要回調的URL callCustomRediretUrl
                if(request.getSession().getAttribute("callCustomRediretUrl") != null && !"".equals(request.getSession().getAttribute("callCustomRediretUrl"))){
                    String url = String.valueOf(request.getSession().getAttribute("callCustomRediretUrl"));
                    //若session 存在則需要使用自定義回撥的URL 而不是快取的URL
                    super.setDefaultTargetUrl(url);
                    super.setAlwaysUseDefaultTargetUrl(true);
                    flag = 1;
                    request.getSession().setAttribute("callCustomRediretUrl", "");
                }
                //重設定預設URL為主頁地址
                if(flag  == 0){
                    super.setDefaultTargetUrl(LOCAL_SERVER_URL);
                }
                super.onAuthenticationSuccess(request, response, authentication);
               
                return;
            }
            //targetUrlParameter 是否存在
            String targetUrlParameter = getTargetUrlParameter();
            if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
                requestCache.removeRequest(request, response);
                super.setAlwaysUseDefaultTargetUrl(false);
                super.setDefaultTargetUrl("/");
                super.onAuthenticationSuccess(request, response, authentication);
                return;
            }
            //清除屬性
            clearAuthenticationAttributes(request);
            // Use the DefaultSavedRequest URL
            String targetUrl = savedRequest.getRedirectUrl();
            logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
            if(targetUrl != null && "".equals(targetUrl)){
                targetUrl = LOCAL_SERVER_URL;
            }
            getRedirectStrategy().sendRedirect(request, response, targetUrl);
        }

        public void setRequestCache(RequestCache requestCache) {
            this.requestCache = requestCache;
        }
}

5 認證失敗控制類重寫
/**
 * <tt>AuthenticationFailureHandler</tt> which performs a redirect to the value of the {@link #setDefaultFailureUrl
 * defaultFailureUrl} property when the <tt>onAuthenticationFailure</tt> method is called.
 * If the property has not been set it will send a 401 response to the client, with the error message from the
 * <tt>AuthenticationException</tt> which caused the failure.
 * <p>
 * If the {@code useForward} property is set, a {@code RequestDispatcher.forward} call will be made to
 * the destination instead of a redirect.
 *
 * @author Luke Taylor
 * @since 3.0
 */
public class MySimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler{

    protected final Log logger = LogFactory.getLog(getClass());

    private String defaultFailureUrl;
    private boolean forwardToDestination = false;
    private boolean allowSessionCreation = true;
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    @Value(value = "${local.service.url}")
    private String LOCAL_SERVER_URL;
    
    public MySimpleUrlAuthenticationFailureHandler() {
    }

    public MySimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
        setDefaultFailureUrl(defaultFailureUrl);
    }

    /**
     * Performs the redirect or forward to the {@code defaultFailureUrl} if set, otherwise returns a 401 error code.
     * <p>
     * If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in
     * the target view.
     */
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        //認證失敗區別前後臺:LOGIN URL
        if(request.getParameter("spring-security-redirect") != null){
              request.getSession().setAttribute("callUrlFailure", request.getParameter("spring-security-redirect"));
        }
        //若有loginUrl 則重定向到後臺登入介面
        if(request.getParameter("loginUrl") != null && !"".equals(request.getParameter("loginUrl"))){
            defaultFailureUrl = LOCAL_SERVER_URL+"/backlogin.html?validated=false";
        }
        //defaultFailureUrl 預設的認證失敗回撥URL
        if (defaultFailureUrl == null) {
            logger.debug("No failure URL set, sending 401 Unauthorized error");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());
        } else {
            saveException(request, exception);
            if (forwardToDestination) {
                logger.debug("Forwarding to " + defaultFailureUrl);
                request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
            } else {
                logger.debug("Redirecting to " + defaultFailureUrl);
                redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
            }
        }
    }

    /**
     * Caches the {@code AuthenticationException} for use in view rendering.
     * <p>
     * If {@code forwardToDestination} is set to true, request scope will be used, otherwise it will attempt to store
     * the exception in the session. If there is no session and {@code allowSessionCreation} is {@code true} a session
     * will be created. Otherwise the exception will not be stored.
     */
    protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
        if (forwardToDestination) {
            request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        } else {
            HttpSession session = request.getSession(false);

            if (session != null || allowSessionCreation) {
                request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
            }
        }
    }

    /**
     * The URL which will be used as the failure destination.
     *
     * @param defaultFailureUrl the failure URL, for example "/loginFailed.jsp".
     */
    public void setDefaultFailureUrl(String defaultFailureUrl) {
        this.defaultFailureUrl = defaultFailureUrl;
    }

    protected boolean isUseForward() {
        return forwardToDestination;
    }

    /**
     * If set to <tt>true</tt>, performs a forward to the failure destination URL instead of a redirect. Defaults to
     * <tt>false</tt>.
     */
    public void setUseForward(boolean forwardToDestination) {
        this.forwardToDestination = forwardToDestination;
    }

    /**
     * Allows overriding of the behaviour when redirecting to a target URL.
     */
    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }

    protected RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }

    protected boolean isAllowSessionCreation() {
        return allowSessionCreation;
    }

    public void setAllowSessionCreation(boolean allowSessionCreation) {
        this.allowSessionCreation = allowSessionCreation;
    }

}

     6 登入Controller和頁面省略