spring security 总共有11个过滤器,依顺序如下:
'SecurityContextPersistenceFilter'
'WebAsyncManagerIntegrationFilter'
'LogoutFilter'
'UsernamePasswordAuthenticationFilter'
'BasicAuthenticationFilter'
'RequestCacheAwareFilter'
'SecurityContextHolderAwareRequestFilter'
'AnonymousAuthenticationFilter'
'SessionManagementFilter'
'ExceptionTranslationFilter'
'FilterSecurityInterceptor'
每个过滤器依次调用。
主要是会用到UsernamePasswordAuthenticationFilter、FilterSecurityInterceptor
bean配置:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd"
>
<bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
<!-- 密码加密类和解密用的盐-->
<bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<property name="userPropertyToUse" value="username"/>
</bean>
<!-- 定义上下文返回的消息的国际化 -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename"
value="classpath:messages_zh_CN"/>
</bean>
<!-- 启用用户的缓存功能 -->
<bean id="userCache"
class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache">
<property name="cache" ref="userEhCache" />
</bean>
<bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheName" value="userCache" />
<property name="cacheManager" ref="cacheManager" />
</bean>
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
<!-- 根据登录帐号查询用户信息 -->
<bean id="userDetailsService" class="com.phms.security.SysUserDetailsService" />
<!-- 配置 免权限访问资源 -->
<sec:http pattern="/**/*.jpg" security="none"/>
<sec:http pattern="/**/*.jpeg" security="none"/>
<sec:http pattern="/**/*.gif" security="none"/>
<sec:http pattern="/**/*.png" security="none"/>
<sec:http pattern="/**/*.js" security="none"/>
<sec:http pattern="/login.jsp" security="none"/>
<!-- security 配置 -->
<sec:http auto-config="true" entry-point-ref="authenticationProcessingFilterEntryPoint">
<!--权限不足处理程序 -->
<sec:access-denied-handler ref="accessDeniedHandler"/>
<!-- 配置登录UsernamePasswordAuthenticationFilter的属性
UsernamePasswordAuthenticationFilter,对用户进行授权,比如进行密码验证。
它会自动注入bean:userDetailsService、passwordEncoder、userCache、messageSource。
如果不需要授权成功或失败后进行特殊处理,下面两行:
authentication-failure-handler-ref="authenticationFailureHandler"
authentication-success-handler-ref="authenticationSuccessHandler
可以配置为:
authentication-failure-url="/login.jsp?error=true"
default-target-url="/welcome”。
login-processing-url="/j_spring_security_check" 登入页面form action 这个不配置的话,默认就是action=“/j_spring_security_check”,可以改
password-parameter="j_password" 帐号默认name="j_username",可以改
username-parameter="j_username" 密码默认name="j_password",可以改
(没用过spring security 的要注意,网上很多介绍spring security 的,但是不给登入页面,新手刚开始不知道默认是这个,然后又不知道哪里配置,
登入后始终达不到想要的目的。)
-->
<sec:form-login login-page="/login.jsp"
login-processing-url="/j_spring_security_check"
password-parameter="j_password"
username-parameter="j_username"
authentication-failure-handler-ref="authenticationFailureHandler"
authentication-success-handler-ref="authenticationSuccessHandler"/>
<!-- 登出配置 -->
<sec:logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" />
<!-- 会话超时转到登录页面 -->
<sec:session-management invalid-session-url="/login.jsp" />
<sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
</sec:http>
<!-- 登入信息验证成功后,登入系统主页 -->
<bean id="authenticationSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/welcome"/>
</bean>
<!-- 登入信息验证失败后,退回到登入页面 -->
<bean id="authenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login.jsp?error=true"/>
</bean>
<!-- 过滤安全拦截器,对用户密码及用户访问资源需要的权限进行验证 -->
<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<!-- 主要是取得资源和权限的关系映射 -->
<property name="securityMetadataSource" ref="securityMetadataSource" />
<!-- 检查必须配置有资源和权限的关系映射的资源可以进行访问
比如:a.jsp没有资源和权限的关系映射,如果此时默认为false,那么直接输入http:localhost:8080/project/a.jsp也是可以访问的。
这样会有隐藏的风险,应该所有资源都配置权限。
-->
<property name="rejectPublicInvocations" value="true"/>
<!-- 1.取得用户信息、用户和权限的关系映射; 2.进行用户密码验证;-->
<property name="authenticationManager" ref="authenticationManager" />
<!-- 如果是true,总是要进行密码验证;如果是False,authenticationManager只会调用一次进行验证,验证成功后,不会再重复调用进行验证。
<property name="alwaysReauthenticate" ref="false" /> -->
<!-- 把 资源和权限的关系映射 与 用户和权限的关系映射 进行权限投票 ,投票通过的资源用户可以访问-->
<property name="accessDecisionManager" ref="accessDecisionManager" />
</bean>
<!-- 验证授权异常,退回到登入页面
authenticationProcessingFilterEntryPoint,accessDeniedHandler什么时候用,可以研究一下ExceptionTranslationFilter。
从ExceptionTranslationFilter知道,
1.authenticationProcessingFilterEntryPoint针对AuthenticationException异常时行 处理,此异常只在authentication的时候使用,Authentication成功后,
不会再进行 Authentication,所以authenticationProcessingFilterEntryPoint,在filterSecurityInterceptor.alwaysReauthenticate默认为false情况下只会在登入时候使用。
2.accessDeniedHandler在用户不为匿名访问情况下,针对AccessDeniedException异常时行 处理,此异常在accessDecisionManager时候使用,
在系统运行时对所有资源进行认证。 所以accessDecisionManager只要用户有访问,它就会被调用。
-->
<bean id="authenticationProcessingFilterEntryPoint" class="com.phms.security.SysLoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp?error=true"/>
</bean>
<!-- 认证权限不足,退回到登入页面
authenticationProcessingFilterEntryPoint,accessDeniedHandler什么时候用,可以研究一下ExceptionTranslationFilter。
从ExceptionTranslationFilter知道,
1.authenticationProcessingFilterEntryPoint针对AuthenticationException异常时行 处理,此异常只在authentication的时候使用,Authentication成功后,
不会再进行 Authentication, 所以authenticationProcessingFilterEntryPoint,在filterSecurityInterceptor.alwaysReauthenticate默认为false情况下只会在登入时候使用。
2.accessDeniedHandler在用户不为匿名访问情况下,针对AccessDeniedException异常时行 处理,此异常在accessDecisionManager时候使用,
在系统运行时对所有资源进行认证。 所以accessDecisionManager只要用户有访问,它就会被调用。
-->
<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl" >
<property name="errorPage" value="/login.jsp?error=denied"/>
</bean>
<!-- 权限验证,主要是把用户权限与资源权限进行比对,如果有相同权限,就算验证通过,可以对资源进行请求
验证通过方式,方式有三种:
一票通过:org.springframework.security.access.vote.AffirmativeBased
多数通过:org.springframework.security.access.vote.ConsensusBased
一票否决:org.springframework.security.access.vote.UnanimousBased
-->
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg name="decisionVoters">
<list>
<ref bean="authenticatedVoter"/>
<ref bean="roleVoter"/>
</list>
</constructor-arg>
<property name="messageSource" ref="messageSource"/>
</bean>
<!-- 投票器。配置它主要是根据用户访问权限进行投票。用户访问权限(为了区别我把它们也称为系统权限), 用户访问权限有三个:
IS_AUTHENTICATED_FULLY(只允许登入用户访问)
IS_AUTHENTICATED_REMEMBERED(允许登入用户和记名用户访问)
IS_AUTHENTICATED_ANONYMOUSLY(允许登入用户、记名用户还有匿名用户访问,实际上就是不做访问权限验证)
投票结果有三个:ACCESS_GRANTED(通过)、ACCESS_ABSTAIN(弃权)、ACCESS_DENIED(否决)
-->
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter" />
<!-- 投票器。配置它主要是:一、对自定义权限前缀进行控制,二、根据自定义权限进行投票。
结果同样有三个:ACCESS_GRANTED(通过)、ACCESS_ABSTAIN(弃权)、ACCESS_DENIED(否决)-->
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
<!-- 权限前缀 此属性不配置的话,默认是"ROLE_",如果不要前缀可以设置为空 -->
<property name="rolePrefix" value="R_"/>
</bean>
<!-- 1.取得用户信息、用户和权限的关系映射; 2.进行用户密码验证;-->
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="authenticationProvider" />
</sec:authentication-manager>
<!-- 引服务提供给spring security authentication manager 用户认证信息,包括权限、密码、密盐-->
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<!-- 此属性为spring security 内置的属性,开启UserNotFoundException-->
<property name="hideUserNotFoundExceptions" value="false" />
<!-- 取得用户信息、用户和权限的关系映射;-->
<property name="userDetailsService" ref="userDetailsService" />
<property name="passwordEncoder" ref="passwordEncoder" />
<property name="saltSource" ref="saltSource" />
</bean>
/**
* 根据用户名,加载用户的认证信息,包括用户的权限和用户登录信息
* 提供给spring security authentication manager 进行认证
* @author swy
*/
public class SysUserDetailsService implements UserDetailsService {
protected final Logger log = LoggerFactory.getLogger(SysUserDetailsService.class);
@Autowired
private MessageSource messageSource;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//此处可以通过数据库访问取得用户信息
Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("R_A");
auths.add(grantedAuthority);
//UserBO 必须实现UserDetails
UserDetails user = new UserBO("1","admin","c3ce8345d3599ab8a4c337d9fb0d0d93",true,true,true,true,auths);
if(log.isDebugEnabled()) log.debug(StringUtils.bean2Str(user.getAuthorities()));
return user;
}
public MessageSource getMessageSource() {
return messageSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public static void main(String[] args) {
//生成MD5加密的密码
MessageDigestPasswordEncoder mp = new MessageDigestPasswordEncoder("MD5");
System.out.println(mp.encodePassword("1", "admin"));
}
}
/**
* 取得资源和权限的关系映射
* @author swy
*
*/
@Service("securityMetadataSource")
public class SysFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private static final Logger log = LoggerFactory.getLogger(SysFilterInvocationSecurityMetadataSource.class);
private final static List<ConfigAttribute> NULL_CONFIG_ATTRIBUTE = Collections.emptyList();
//权限集合
private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
@Override
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
System.out.println("URL资源:"+request.getRequestURI()+ " -> ");
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : this.bindRequestMap().entrySet()) {
if (entry.getKey().matches(request)) {
LogUtils.autolog(log,"URL资源:"+request.getRequestURI()+ " -> " + entry.getValue());
return entry.getValue();
}
}
return NULL_CONFIG_ATTRIBUTE;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : this.bindRequestMap().entrySet()) {
allAttributes.addAll(entry.getValue());
}
return allAttributes;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
private Map<String,String> loadResuorce(){
Map<String,String> map = new LinkedHashMap<String,String>();
//此处可以通过数据库访问取得用户信息
List<Map<String,String>> list = new ArrayList<Map<String,String>>();
map.put("/welcome", "R_A,IS_AUTHENTICATED_ANONYMOUSLY");
map.put("/menu/*", "R_A,R_B");
map.put("/order/*", "R_A,R_B");
return map;
}
protected Map<RequestMatcher, Collection<ConfigAttribute>> bindRequestMap(){
Map<RequestMatcher, Collection<ConfigAttribute>> map =
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
Map<String,String> resMap = this.loadResuorce();
for(Map.Entry<String,String> entry:resMap.entrySet()){
String key = entry.getKey();
Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
atts = SecurityConfig.createListFromCommaDelimitedString(entry.getValue());
map.put(new AntPathRequestMatcher(key), atts);
}
return map;
}
@PostConstruct
public void afterPropertiesSet() throws Exception{
this.requestMap = this.bindRequestMap();
}
public void refreshResuorceMap(){
this.requestMap = this.bindRequestMap();
}
}
/**
验证授权异常,退回到登入页面
authenticationProcessingFilterEntryPoint,accessDeniedHandler什么时候用,可以研究一下ExceptionTranslationFilter。
从ExceptionTranslationFilter知道,
1.authenticationProcessingFilterEntryPoint针对AuthenticationException异常时行 处理,此异常只在authentication的时候使用,Authentication成功后,不会再进行 Authentication,
所以authenticationProcessingFilterEntryPoint,在filterSecurityInterceptor.alwaysReauthenticate默认为false情况下只会在登入时候使用。
2.accessDeniedHandler在用户不为匿名访问情况下,针对AccessDeniedException异常时行 处理,此异常在accessDecisionManager时候使用,在系统运行时对所有资源进行认证。
所以accessDecisionManager只要用户有访问,它就会被调用。
* @author swy
*
*/
@SuppressWarnings("deprecation")
public class SysLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
private static final Log logger = LogFactory.getLog(SysLoginUrlAuthenticationEntryPoint.class);
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("SysLoginUrlAuthenticationEntryPoint: " + getLoginFormUrl());
}
String redirectUrl = null;
if (isUseForward()) {
if (isForceHttps() && "http".equals(request.getScheme())) {
// First redirect the current request to HTTPS.
// When that request is received, the forward to the login page will be used.
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl == null) {
String loginForm = determineUrlToUseForThisRequest(request, response, authException);
if (logger.isDebugEnabled()) {
logger.debug("Server side forward to: " + loginForm);
}
// 传递异常到页面
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, authException);
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
} else {
// redirect to login page. Use https if forceHttps true
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
//传递异常到页面
request.getSession(true).setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, authException);
redirectStrategy.sendRedirect(request, response, redirectUrl);
}
}
}
login.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>登入</title>
</head>
<body>
<table width="100%" height="100%" bgcolor="#FFFFFF">
<tr align="center">
<td>
<div class="easyui-panel" title="登入" style="width:400px;height:auto;" align="center">
<br/>
<form action="${pageContext.request.contextPath}/j_spring_security_check" method="post">
<!-- 显示登入失败信息 ${sessionScope['SPRING_SECURITY_LAST_EXCEPTION']} -->
<div style="display:${param.error == true ? 'block' : 'none'}">
<font color="red" size="10">登入失败:${sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message}</font>
</div>
<div style="display:${param.error == 'denied' ? 'block' : 'none'}">
<font color="red" size="10">登入失败2:${SPRING_SECURITY_403_EXCEPTION.message}</font>
</div>
帐号:<input name="j_username"><br/><br/>
密码:<input type="password" name="j_password"><br/><br/>
<input type="submit" value="登陆">
</form>
</div>
</td>
</tr>
</table>
</body>
</html>