1. 程式人生 > >spring-security4.2實現登入退出以及許可權配置

spring-security4.2實現登入退出以及許可權配置

最近用到了spring-security框架來實現登入驗證。
以前做登入的步驟是:
1、使用者輸入使用者名稱、密碼登入
2、連線資料庫對使用者名稱、密碼進行驗證
3、獲取使用者資訊(角色列表等等)
4、獲取相關操作許可權
security安全框架有點不同:
1、使用者名稱、密碼組合生成一個AuthenticationToken物件。
2、生成的這個token物件會傳遞給一個AuthenticationManager物件用於驗證。
3、當成功認證後,AuthenticationManager返回一個Authentication物件。
4、接下來,就可以呼叫AuthenticationSuccessHandler成功處理器跳轉首頁或者登入之前訪問的url。

先上spring-security.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"
>
<http pattern="/skin/**" security="none" /> <http pattern="/login.action" security="none" /> <http authentication-manager-ref="authenticationManager" entry-point-ref="customAuthenticationEntryPoint"> <!-- 初始化 --> <intercept-url pattern="/init/**"
access="hasRole('ROLE_ADMIN')" />
<!-- 登入 --> <intercept-url pattern="/login.action*" access="permitAll" /> <!-- 使用者管理(如果多個許可權可以訪問就用hasAnyRole('xx','cc')) --> <intercept-url pattern="/user/*.action" access="hasRole('ROLE_ADMIN')" /> <!-- 其他 --> <intercept-url pattern="/**" access="authenticated" /> <!-- 自定義認證過濾器 --> <custom-filter ref="customAuthenticationFilter" position="FORM_LOGIN_FILTER" /> <!-- 自定義退出成功處理器 --> <logout logout-url="/logout.action" success-handler-ref="customLogoutSuccessHandler" /> <headers> <!-- Iframe頁面允許被其它頁面嵌入 --> <frame-options disabled="true" /> </headers> <csrf disabled="true" /> </http> <!-- 認證管理器 --> <authentication-manager alias="authenticationManager"> <authentication-provider ref="customAuthenticationProvider" /> </authentication-manager> <!-- 認證服務提供者 --> <beans:bean id="customAuthenticationProvider" class="com.identity.security.CustomAuthenticationProvider" /> <!-- 認證入口點 --> <beans:bean id="customAuthenticationEntryPoint" class="com.identity.security.CustomAuthenticationEntryPoint"> <beans:constructor-arg name="pcLoginUrl" value="/login.action" /> </beans:bean> <!-- 認證過濾器 --> <beans:bean id="customAuthenticationFilter" class="com.identity.security.CustomAuthenticationFilter"> <beans:constructor-arg name="filterProcessesUrl" value="/doLogin.action" /> <beans:property name="authenticationSuccessHandler" ref="customAuthenticationSuccessHandler" /> <beans:property name="authenticationFailureHandler" ref="customAuthenticationFailureHandler" /> <beans:property name="authenticationManager" ref="authenticationManager" /> </beans:bean> <!-- 登入認證成功處理器 --> <beans:bean id="customAuthenticationSuccessHandler" class="com.identity.security.CustomAuthenticationSuccessHandler" /> <!-- 登入認證失敗處理器 --> <beans:bean id="customAuthenticationFailureHandler" class="com.identity.security.CustomAuthenticationFailureHandler" /> <!-- 退出登入處理器 --> <beans:bean id="customLogoutSuccessHandler" class="com.identity.security.CustomLogoutSuccessHandler" /> </beans:beans>

先配置一個自定義登入頁面

package com.identity.security;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

/**
 * 自定義認證入口點
 * 
 */
public class CustomAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    /**
     * 構造方法
     * 
     * @param pcLoginUrl 登入頁
     */
    public CustomAuthenticationEntryPoint(String pcLoginUrl) {
        super(pcLoginUrl);
    }

    @Override
    protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        return super.determineUrlToUseForThisRequest(request, response, exception);
    }
}

建立一個自定義的token物件類

package com.identity.security;

import org.springframework.security.authentication.AbstractAuthenticationToken;

import com.identity.entitys.UserInfo;

/**
 * 自定義認證token
 * 
 */
public class CustomAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = 1L;

    /** Principal */
    private Object principal;

    /** 帳號 */
    private String username;

    /** 密碼 */
    private String password;

    /** 登入IP */
    private String loginIp;

    /** 構造方法,未通過登入認證 */
    public CustomAuthenticationToken() {
        super(null);
        this.principal = null;
        super.setAuthenticated(false);
    }

    /** 構造方法,已經通過登入認證 */
    public CustomAuthenticationToken(UserInfo user) {
        super(user.getAuthoritys());
        this.principal = user;
        super.setAuthenticated(true);
    }

    /**
     * 獲取帳號
     * 
     * @return username 帳號
     */
    public String getUsername() {
        return username;
    }

    /**
     * 設定帳號
     * 
     * @param username 帳號
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 獲取密碼
     * 
     * @return password 密碼
     */
    public String getPassword() {
        return password;
    }

    /**
     * 設定密碼
     * 
     * @param password 密碼
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 獲取登入IP
     * 
     * @return loginIp 登入IP
     */
    public String getLoginIp() {
        return loginIp;
    }

    /**
     * 設定登入IP
     * 
     * @param loginIp 登入IP
     */
    public void setLoginIp(String loginIp) {
        this.loginIp = loginIp;
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }
}

使用者輸入完賬號密碼後會先進這個認證濾器生成我們定義好的token物件

package com.identity.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

/**
 * 自定義認證過濾器
 */
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationFilter.class);

    /** 構造方法,設定登入URL */
    public CustomAuthenticationFilter(String filterProcessesUrl) {
        super(filterProcessesUrl);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        try {
            CustomAuthenticationToken token = new CustomAuthenticationToken();
            token.setUsername(request.getParameter("username"));
            token.setPassword(request.getParameter("password"));
            token.setLoginIp(getRequestIp(request));
            token.setDetails(authenticationDetailsSource.buildDetails(request));
            return this.getAuthenticationManager().authenticate(token);
        } catch (CustomAuthenticationException e) {
            throw e;
        } catch (Exception e) {
            LOGGER.error("登入過程異常,請求引數為[" + request + "]", e);
            throw new CustomAuthenticationException("登入失敗,伺服器內部錯誤,請稍後再試...");
        }
    }

    /** 獲取請求客戶端真實IP */
    public String getRequestIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}

然後過濾器把這個Authentication物件傳遞給AuthenticationProvider去處理

package com.identity.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.transaction.annotation.Transactional;

import com.identity.entitys.LoginLog;
import com.identity.entitys.UserInfo;
import com.identity.querys.impl.UserInfoQueryImpl;

/**
 * 自定義認證服務提供者
 * 
 */
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationProvider.class);

    @Override
    @Transactional()
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        try {
            CustomAuthenticationToken token = (CustomAuthenticationToken) authentication;
            UserInfo user = retrieveUser(token);
            preAuthenticationCheck(token, user);
            additionalAuthenticationCheck(token, user);
            postAuthenticationCheck(token, user);
            saveLoginLog(token, user);
            CustomAuthenticationToken result = new CustomAuthenticationToken(user);
            result.setDetails(authentication.getDetails());
            return result;
        } catch (CustomAuthenticationException e) {
            throw e;
        } catch (Exception e) {
            LOGGER.error("登入認證異常,Token為[" + authentication + "]", e);
            throw new CustomAuthenticationException("登入失敗,伺服器內部錯誤,請稍後再試...", e);
        }
    }

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

    /** 檢索使用者 */
    private UserInfo retrieveUser(CustomAuthenticationToken token) {
    //這裡是進資料庫根據賬號查使用者
        UserInfo user = null;
        user = new UserInfoQueryImpl().username(token.getUsername(),false).uniqueResult();
        return user;
    }

    /** 前置的身份認證檢查 */
    private void preAuthenticationCheck(CustomAuthenticationToken token, UserInfo user) {
        if (user == null) {
            throw new CustomAuthenticationException("登入失敗,帳號不存在");
        }
        if (!user.isEnabled()) {
            throw new CustomAuthenticationException("登入失敗,您的帳號已被禁用");
        }
        if (!user.isAccountNonExpired()) {
            throw new CustomAuthenticationException("登入失敗,您的帳號已過期");
        }
    }

    /** 後置的身份認證檢查 */
    private void postAuthenticationCheck(CustomAuthenticationToken token, UserInfo user) {
        if (!user.isCredentialsNonExpired()) {
            throw new CustomAuthenticationException("登入失敗,您的密碼已過期");
        }
    }

    /** 額外的身份認證檢查 */
    public void additionalAuthenticationCheck(CustomAuthenticationToken token, UserInfo user) {
        if (!user.isRealPassword(token.getPassword())) {
            throw new CustomAuthenticationException("帳號或密碼錯誤");
        }
    }

    /** 儲存登入日誌 */
    public void saveLoginLog(CustomAuthenticationToken token, UserInfo user) {
        LoginLog loginLog = new LoginLog();
        loginLog.setIp(token.getLoginIp());
        loginLog.setUser(user);
        loginLog.saveOrUpdateIt();
    }
}

在AuthenticationProvider裡面驗證後會進入登入成功或者失敗處理器

package com.identity.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;

/**
 * 自定義登入認證成功處理器
 * 
 */
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

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

        response.setContentType("text/html;charset=UTF-8");
        JSONObject jsonObject = new JSONObject();
        String targetUrl = request.getParameter("to");
        if (StringUtils.isEmpty(targetUrl)) {
            DefaultSavedRequest savedRequest = (DefaultSavedRequest) this.requestCache.getRequest(request, response);
            if (savedRequest != null) {
                targetUrl = savedRequest.getRequestURI() + "?" + savedRequest.getQueryString();
            } else {
                targetUrl = request.getContextPath() + "/index.action";
            }
        } else {
            this.requestCache.removeRequest(request, response);
        }
        clearAuthenticationAttributes(request);
        jsonObject.put("staut", true);
        jsonObject.put("targetUrl", targetUrl);
        response.getWriter().write(jsonObject.toString());
    }

    /** 刪除身份認證臨時資料 */
    protected final void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return;
        }
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }
}
package com.identity.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

/**
 * 自定義登入認證失敗處理器
 * 
 */
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        String errorMsg = null;
        JSONObject jsonObject = new JSONObject();
        if (exception instanceof CustomAuthenticationException) {
            errorMsg = exception.getMessage();
        } else {
            LOGGER.error("登入異常,請求引數為[" + request + "]", exception);
            errorMsg = "登入失敗,伺服器內部錯誤,請稍後再試...";
        }
        jsonObject.put("staut", false);
        jsonObject.put("errorMsg", errorMsg);
        response.getWriter().write(jsonObject.toString());
    }
}

自定義異常類

package com.identity.security;

import org.springframework.security.core.AuthenticationException;

/**
 * 自定義認證異常
 * 
 */
public class CustomAuthenticationException extends AuthenticationException {

    private static final long serialVersionUID = 1L;

    public CustomAuthenticationException(String msg) {
        super(msg);
    }

    public CustomAuthenticationException(String msg, Throwable t) {
        super(msg, t);
    }
}

然後是退出

package com.identity.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
 * 自定義退出登入處理器
 * 
 */
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        JSONObject jsonObject = new JSONObject();
        String url = request.getParameter("to");
        if (StringUtils.isEmpty(url)) {
            url = request.getContextPath() + "/login.action?logout=true";
        }
        jsonObject.put("staut", true);
        jsonObject.put("url", url);
        response.getWriter().write(jsonObject.toString());
    }
}

然後使用者和許可權實體多對多的關係就行了,許可權表的許可權名記得是ROLE_XX的格式。完成以上的配置基本上登入退出就完成了~~