spring-security4.2实现登录退出以及权限配置
程序员文章站
2024-03-19 14:06:22
...
最近用到了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的格式。完成以上的配置基本上登录退出就完成了~~
上一篇: RBAC基于角色的权限访问控制