spring-security4.2實現登入退出以及許可權配置
阿新 • • 發佈:2019-02-05
最近用到了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的格式。完成以上的配置基本上登入退出就完成了~~