Spring Security 是一个能够为基于 Spring 的企业应用系统提供描述性安全访问控制解决方案的安全框架。由于本人今天对此框架学习了一番,为了保留学习成果,提供以后开发使用。在这里对此开发配置流程,进行详细说明记录。
结合使用此框架需要引入如下 Jar 包,由于本人使用 maven 结构工程,只提供 POM 方式的配置引入:
<!-- 版本号 --> <spring.security.version>3.1.4.RELEASE</spring.security.version> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-acl</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-openid</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring.security.version}</version> </dependency>
接下来我们需要配置 web.xml 文件,当然你的工程必须已经是一个引入了 Spring 框架的 WebApp:
<!-- Spring Config File Path --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:security-config.xml</param-value><!-- 这里配置 权限的配置文件读取地址 --> </context-param> <!-- Spring Secutiry 过滤链配置 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
web.xml 文件中的过滤器 springSecurityFilterChain 的 <filter-name> 不可以更改。如果随意指定过滤器名称,启动后,会报错:
2013-09-06 13:21:58,992 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(502)] - No bean named 'SecurityFilterChain' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@2dc0435: defining beans [org.springframework.security.filterChains,org.springframework.security.filterChainProxy,org.springframework.security.web.DefaultSecurityFilterChain#0,org.springframework.security.web.PortMapperImpl#0,org.springframework.security.web.PortResolverImpl#0,org.springframework.security.config.authentication.AuthenticationManagerFactoryBean#0,org.springframework.security.authentication.ProviderManager#0,org.springframework.security.web.context.HttpSessionSecurityContextRepository#0,org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy#0,org.springframework.security.web.savedrequest.HttpSessionRequestCache#0,org.springframework.security.access.vote.AffirmativeBased#0,org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0,org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator#0,org.springframework.security.authentication.AnonymousAuthenticationProvider#0,org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices#0,org.springframework.security.authentication.RememberMeAuthenticationProvider#0,org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint#0,org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0,org.springframework.security.userDetailsServiceFactory,org.springframework.security.web.DefaultSecurityFilterChain#1,iFilter,org.springframework.security.authentication.dao.DaoAuthenticationProvider#0,org.springframework.security.authentication.DefaultAuthenticationEventPublisher#0,org.springframework.security.authenticationManager,userDetailsManager,accessDecisionManager,securityMetadataSource,passwdEcoder]; root of factory hierarchy 2013-9-6 13:21:58 org.apache.catalina.core.StandardContext filterStart 严重: Exception starting filter SecurityFilterChain org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'SecurityFilterChain' is defined at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:504) at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1041) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:273) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1008) at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:217) at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:145) at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:179) at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:295) at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:422) at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:115) at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4072) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4726) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1057) at org.apache.catalina.core.StandardHost.start(StandardHost.java:840) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1057) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:463) at org.apache.catalina.core.StandardService.start(StandardService.java:525) at org.apache.catalina.core.StandardServer.start(StandardServer.java:754) at org.apache.catalina.startup.Catalina.start(Catalina.java:595) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) 2013-9-6 13:21:58 org.apache.catalina.core.StandardContext start
此过滤器必须配置,不然无法使用 Spring Security 框架对访问权限的控制。
此 外,还必须在权限配置文件 security-config.xml 中,配置 <http> 并设定其属性 auto-config="true",才能保证 WebApp 工程正常的启动。下面让我们看一下,我的 security-config.xml 是如何配置的。
<?xml version="1.0" encoding="UTF-8"?> <b:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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.1.xsd"> <!-- 登录页面不过滤 --> <http pattern="/login" security="none"/> <http auto-config="true"> <!-- 登录配置 --> <form-login login-page="/login" authentication-failure-url="/login?err=true" default-target-url="/demo/list" username-parameter="j_username" password-parameter="j_password" login-processing-url="/j_spring_security_check"/> <!-- 退出配置 --> <!-- logout-url:退出请求地址。系统默认:j_spring_security_logout logout-success-url:退出成功,跳转地址连接。 delete-cookies:删除 cookies 内容。 success-handler-ref:退出回调接口。类需实现接口: LogoutSuccessHandler invalidate-session:如果设置为 true,用户的 Session 将会在退出时被失效。 --> <logout logout-success-url="/index.html" invalidate-session="true"/> <remember-me /> <!-- 自定义过滤器, 实现用户、角色、权限、资源的数据库管理 --> <custom-filter ref="iFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </http> <!-- 自定义过滤器 --> <b:bean id="iFilter" class="org.lei.core.filter.SecurityInterceptorDemo"> <b:property name="securityMetadataSource" ref="securityMetadataSource"/><!-- FilterInvocationSecurityMetadataSource 接口实现类 --> <b:property name="authenticationManager" ref="authenticationManager"/><!-- 鉴定管理类 --> <b:property name="accessDecisionManager" ref="accessDecisionManager"/><!-- AccessDecisionManager 接口实现类 --> </b:bean> <!-- 鉴定管理类配置信息 --> <authentication-manager alias="authenticationManager"><!-- 鉴定管理类 --> <authentication-provider user-service-ref="userDetailsManager"><!-- 用户详情管理类 [UserDetailsService 接口 实现类] --> <password-encoder ref="passwdEcoder"><!-- 用户加密解密类 --> <salt-source user-property="username"/> </password-encoder> </authentication-provider> </authentication-manager> <!-- 用户详细信息获取接口 --> <b:bean id="userDetailsManager" class="org.lei.core.filter.CustomUserDetailsService"/> <!-- 访问决策器, 决定某个用户具体的角色,是否有足够的权限访问某个资源 --> <b:bean id="accessDecisionManager" class="org.lei.core.filter.CustomAccessDecisionManager"/> <!-- 资源源数据定义, 将所有的资源和权限的对应关系建立起来,即定义某一资源可以被哪些角色去访问。--> <b:bean id="securityMetadataSource" class="org.lei.core.filter.CustomSecurityMetadataSource"/> <!-- 用户详情管理类使用的加密方式 --> <b:bean id="passwdEcoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/><!-- PasswordEncoder 密码接口 --> </b:beans>
关于 xml 配置,这里我将此文件默认的命名空间指定为 xmlns="http://www.springframework.org/schema/security" ,所以我们使用 security 定义的标签不需要添加任何前缀。如:<http>。这里需要注意的是,我使用的 xml 样式是 http://www.springframework.org/schema/security/spring-security-3.1.xsd 版本的。所以和网上大多数配置了 http://www.springframework.org/schema/security/spring-security-3.0.xsd 配置方法会不太一样。如果你按照我的配置设置,出现了报错情况,请检查下自己引用的 xsd 是那个版本的。
<?xml version="1.0" encoding="UTF-8"?> <b:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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.1.xsd"> …… </b:beans>
如果你不需要对某些访问路径,进行权限控制。可以配置:
<!-- 登录页面不过滤 --> <http pattern="/login" security="none"/>
配置中,对登录页面的访问,不进行权限控制。如果你还有其他的配置需求,可以继续添加。
现在我们说一说最重要的配置, <http> 标签。其标签中,可以配置很多项目,因为自己研究有限,就只说说我知道的那几块。
<form-login /> 登录表单标签
<!-- login-page:登录页面地址 authentication-failure-url:登录失败页面地址 default-target-url:登录成功跳转页面地址 login-processing-url:登录表单提交地址。系统默认: j_spring_security_check username-parameter:表单中,用户名参数提交名称。系统默认: j_username password-parameter:表单中,用户密码参数提交名称。系统默认: j_password --> <form-login login-page="/login" authentication-failure-url="/login?err=true" default-target-url="/demo/list" username-parameter="j_username" password-parameter="j_password" login-processing-url="/j_spring_security_check"/>
此类会调用到 UsernamePasswordAuthenticationFilter 类 中的 attemptAuthentication 方法:
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); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
如方法中,最先对请求方式的判断。所以提交的表单时,必须以 Post 方式提交。之后,再通过
obtainUsername 和 obtainPassword 方法获取提交的用户名和密码。
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); }
由上面提供的源码片段,你应该明白些什么了吧。呵呵~
此类的调用在 AbstractAuthenticationProcessingFilter 的 doFilter 方法中。
<logout /> 退出登录标签
<!-- logout-url:退出请求地址。系统默认:j_spring_security_logout logout-success-url:退出成功,跳转地址连接。 delete-cookies:删除 cookies 内容。 success-handler-ref:退出回调接口。类需实现接口: LogoutSuccessHandler invalidate-session:如果设置为 true,用户的 Session 将会在退出时被失效。 --> <logout logout-success-url="/index.html" invalidate-session="true"/>
这里我没有做太多研究,基本配置是可以满足日常开发了,若以后有扩充,我到时候再补充吧。
重点中的重点来了,如何实现将你设计的用户、权限、角色关系,适用于 Spring Security 框架来帮你管理呢。这里我先把我理解的,此框架的原理描述下:
1、一个 URL 访问地址称为资源,能不能访问这个资源是取决于权限。所以,在 Spring Security 框架中,需要维护一个资源和权限的映射关系,这种关系是 1..n 的,即 一个资源对应多个权限。
2、权限,这里也可以理解为角色。角色必定有自己的角色名称,而对于 Spring Security 要的就是这个角色名称,让它和资源实现映射关系。比如:
角色A和角色B,都可以访问地址 /xxx/list。在 Spring Security 框架中,其维护结构为 key=/xxx/list, value=[角色A, 角色B]。框架可以通过访问地址找到哪些角色可以访问。
3、用户在完成登录后,用户信息中会保存自己拥有的权限,也就是角色。当用户每次产生访问行为时,都会和此访问资源对应的权限比较,如果访问资源存在此角色,即用户可以正常访问,反之报错。
以上大体就是 Spring Security 框架验证的一个过程,下面我们就开始介绍如何使用和开发自定义的验证流程:
第一步,先配置自定义过滤器,在 <http> 标签中。
<!-- 自定义过滤器, 实现用户、角色、权限、资源的数据库管理 --> <custom-filter ref="iFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
一看 ref="iFilter",就知道引用了一个类。看下面配置:
<!-- 自定义过滤器 --> <b:bean id="iFilter" class="org.lei.core.filter.SecurityInterceptorDemo"> <b:property name="securityMetadataSource" ref="securityMetadataSource"/><!-- FilterInvocationSecurityMetadataSource 接口实现类 --> <b:property name="authenticationManager" ref="authenticationManager"/><!-- 鉴定管理类 --> <b:property name="accessDecisionManager" ref="accessDecisionManager"/><!-- AccessDecisionManager 接口实现类 --> </b:bean>
编写自定义过滤器,必须继承 org.springframework.security.access.intercept.AbstractSecurityInterceptor 和 实现 javax.servlet.Filter 接口。重写 doFilter 方法并新增属性 FilterInvocationSecurityMetadataSource 对象的 getter 和 setter 方法,用于 Spring 注入。
package org.lei.core.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; /** * * @ClassName SecurityInterceptorDemo * @Description TODO * @author * @date 2013-9-5 上午10:20:11 * @version 1.0 * */ public class SecurityInterceptorDemo extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; /** * @param filterConfig * @throws ServletException */ @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * @param request * @param response * @param chain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public void invoke(FilterInvocation fi) { InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch (Exception e) { super.afterInvocation(token, null); } } /** * */ @Override public void destroy() { } /** * @return */ @Override public Class<? extends Object> getSecureObjectClass() { return FilterInvocation.class; } /** * @return */ @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } }
如之前的配置,我们需要 Spring 帮我们注入 securityMetadataSource、authenticationManager、accessDecisionManager 三个类对象。那他们分别是做什么的呢?现在听我慢慢道来。
securityMetadataSource 这个类型的接口,提供了根据访问资源获取角色集合的接口,也就是说此类维护着,资源和角色的关系并提供外界使用。 securityMetadataSource 必须是 FilterInvocationSecurityMetadataSource 接口实现类。看看我写的自定义实现类:
package org.lei.core.filter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.AntPathRequestMatcher; import org.springframework.security.web.util.RequestMatcher; /** * * @ClassName CustomSecurityMetadataSource * @Description TODO * @author zhuzhonglei * @date 2013-9-5 上午10:50:30 * @version 1.0 * */ public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { /** * LOGGER 日志对象 */ private final static Logger LOGGER = Logger.getLogger(CustomSecurityMetadataSource.class); private HashMap<String, Collection<ConfigAttribute>> map = new HashMap<String, Collection<ConfigAttribute>>(); /** * 加载资源,初始化资源变量 * */ private void loadResourceDefine() { Collection<ConfigAttribute> array = new ArrayList<ConfigAttribute>(4); ConfigAttribute cfg = new SecurityConfig("a1"); array.add(cfg); cfg = new SecurityConfig("a2"); array.add(cfg); cfg = new SecurityConfig("a3"); array.add(cfg); cfg = new SecurityConfig("a4"); array.add(cfg); map.put("/demo/list", array); array = new ArrayList<ConfigAttribute>(4); cfg = new SecurityConfig("n1"); array.add(cfg); cfg = new SecurityConfig("n2"); array.add(cfg); map.put("/demo/news", array); } public CustomSecurityMetadataSource() { loadResourceDefine(); } /** * 根据路径获取访问权限的集合接口 * @param object * @return * @throws IllegalArgumentException */ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { LOGGER.info(object); HttpServletRequest request = ((FilterInvocation)object).getHttpRequest(); RequestMatcher matcher = null; String resUrl = null; for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) { resUrl = iter.next(); matcher = new AntPathRequestMatcher(resUrl); if (null != resUrl && matcher.matches(request)) { return map.get(resUrl); } } return null; } /** * @return */ @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } /** * @param clazz * @return */ @Override public boolean supports(Class<?> clazz) { return true; } }
我在 loadResourceDefine 方法中,初始化了资源。将资源和权限以 Map 的形式做了映射。一个地址会对应一组权限。然后实现了接口方法 public Collection<ConfigAttribute> getAttributes(Object object) ,可以通过此方法获取权限集合。这个接口的调用在 AbstractSecurityInterceptor 的 beforeInvocation 方法中。
authenticationManager 实现类由 Spring 提供,我们使用配置对其声明:
<!-- 鉴定管理类配置信息 --> <authentication-manager alias="authenticationManager"><!-- 鉴定管理类 --> <authentication-provider user-service-ref="userDetailsManager"><!-- 用户详情管理类 [UserDetailsService 接口 实现类] --> <password-encoder ref="passwdEcoder"><!-- 用户加密解密类 --> <salt-source user-property="username"/> </password-encoder> </authentication-provider> </authentication-manager>
该配置中 userDetailsManager 和 passwdEcoder。userDetailsManager 是用户登录时,通过此接口获取 UserDetails 接口对象。配置接口实现类:
<!-- 用户详细信息获取接口 --> <b:bean id="userDetailsManager" class="org.lei.core.filter.CustomUserDetailsService"/>
自定义 CustomUserDetailsService 类,实现接口 UserDetailsService。
import java.util.ArrayList; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * * @ClassName CustomUserDetailsService * @Description TODO * @author * @date 2013-9-5 下午1:19:33 * @version 1.0 * */ public class CustomUserDetailsService implements UserDetailsService { /** * @param username * @return * @throws UsernameNotFoundException * @throws DataAccessException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { ArrayList<GrantedAuthority> array = new ArrayList<GrantedAuthority>(); GrantedAuthority ga = new SimpleGrantedAuthority("a1"); array.add(ga); return new User("demo", "8ae29a58361c8b3ec237ae8419df7e58", true, true, true, true, array); } }
实现类中,我们可以通过 loadUserByUsername 方法,根据用户名找到该用户的基本信息和角色信息。并创建 UserDetails 实现类对象返回。我们在这里设置了角色集合对象 array 并将其赋值给了User 对象。
passwdEcoder 的应用使用类 Spring 提供的 Md5PasswordEncoder,配置如下:
<!-- 用户详情管理类使用的加密方式 --> <b:bean id="passwdEcoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/><!-- PasswordEncoder 密码接口 -->
这个加密如配置一样,使用的加盐方式。将用户名和密码合并进行加密。具体加密方式,看源码得知:
protected String mergePasswordAndSalt(String password, Object salt, boolean strict) { if (password == null) { password = ""; } if (strict && (salt != null)) { if ((salt.toString().lastIndexOf("{") != -1) || (salt.toString().lastIndexOf("}") != -1)) { throw new IllegalArgumentException("Cannot use { or } in salt.toString()"); } } if ((salt == null) || "".equals(salt)) { return password; } else { return password + "{" + salt.toString() + "}"; } }
例如:密码为 123456,用户名为 demo, 那就会将 123456{demo} 进行MD5 加密比较。所以,在新增用户或设置密码时,也要按照这样的方式加密存入数据库,不然用户登录这块,密码将验证不通过。
现 在,我们分别实现了 FilterInvocationSecurityMetadataSource 接口和 UserDetailsService 接口。FilterInvocationSecurityMetadataSource 接口实现类维护着资源与权限的映射关系,而 UserDetailsService 接口又维护着将登陆用户信息封装到用户对象中。这时候,当用户需要访问某个资源是,我们就可以通过这两个对象在 accessDecisionManager 引用的 AccessDecisionManager 接口实现类中,进行比较了。
我们先在配置文件中,配置该类:
<!-- 访问决策器, 决定某个用户具体的角色,是否有足够的权限访问某个资源 --> <b:bean id="accessDecisionManager" class="org.lei.core.filter.CustomAccessDecisionManager"/>
实现类 CustomAccessDecisionManager :
package org.lei.core.filter; import java.util.Collection; import java.util.Iterator; import org.apache.log4j.Logger; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; /** * * @ClassName CustomAccessDecisionManager * @Description TODO * @author * @date 2013-9-5 上午11:46:35 * @version 1.0 * */ public class CustomAccessDecisionManager implements AccessDecisionManager { /** * LOGGER 日志对象 */ private final static Logger LOGGER = Logger.getLogger(CustomAccessDecisionManager.class); /** * @param authentication * @param object * @param configAttributes * @throws AccessDeniedException * @throws InsufficientAuthenticationException */ @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { LOGGER.info("CustomAccessDecisionManager.decide"); if (null == configAttributes || configAttributes.size() <= 0) { return; } ConfigAttribute c = null; String needRole = null; for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) { c = iter.next(); needRole = c.getAttribute(); LOGGER.info("菜单访问权限:" + needRole); for (GrantedAuthority ga : authentication.getAuthorities()) { if (needRole.trim().equals(ga.getAuthority())) { return; } } } throw new AccessDeniedException("结束,没有权限!"); } /** * @param attribute * @return */ @Override public boolean supports(ConfigAttribute attribute) { return true; } /** * @param clazz * @return */ @Override public boolean supports(Class<?> clazz) { return true; } }
decide 方法接受三个参数,第一个使用户拥有的角色,第二个参数就是在 过滤器中新建的 FilterInvocation 对象,第三个参数就是该访问路径对应的角色集合。然后可以根据 用户角色和菜单角色对比,判断该用户是否具有该路径的访问资格。如果没有,抛出异常。
以上就是大体的开发流程和基本原理了,在这里做已记录。
附件为整理时画的图。
相关推荐
linux基础进阶笔记,配套视频:https://www.bilibili.com/list/474327672?sid=4493093&spm_id_from=333.999.0.0&desc=1
IMG20241115211541.jpg
GEE训练教程——Landsat5、8和Sentinel-2、DEM和各2哦想指数下载
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
基于springboot家政预约平台源码数据库文档.zip
Ucharts添加stack和折线图line的混合图
基于springboot员工在线餐饮管理系统源码数据库文档.zip
新能源汽车进出口数据 1、时间跨度:2018-2020年 2、指标说明:包含如下指标的进出口数据:混合动力客车(10座及以上)、纯电动客车(10座及以上)、非插电式混合动力乘用车、插电式混合动力乘用车、纯电动乘用车 二、新能源汽车进出口月销售数据(分地区、分类型、分 级别) 1、数据来源:见资料内说明 2、时间跨度:2014年1月-2021年5月 4、指标说明: 包含如下指标 2015年1月-2021年5月新能源乘用车终端月度销量(分类型)部分内容如下: 新能源乘用车(单月值、累计值 )、插电式混合动力 月度销量合计(狭义乘用车轿车、SUV、MPV、交叉型乘用车); 月度销量同比增速(狭义乘用车轿车、SUV、MPV、交叉型乘用车); 累计销量合计(狭义乘用车轿车、SUV、IPV、交叉型乘用车); 累计销量同比增速(狭义乘用车轿车、SUV、MPV、交叉型乘用车); 累计结构变化(狭义乘用车轿车、SUV、IPV、交叉型乘用车); 2015年1月-2021年5月新能源乘用车终端月度销量(分地区)内容如下: 更多见资源内
中心主题-241121215200.pdf
内容概要:本文档提供了多个蓝奏云下载链接及其对应解压密码,帮助用户快速获取所需文件。 适合人群:需要从蓝奏云下载文件的互联网用户。 使用场景及目标:方便地记录并分享蓝奏云上文件的下载地址和密码,提高下载效率。 阅读建议:直接查看并使用提供的链接和密码即可。若遇到失效情况,请尝试联系上传者确认更新后的链接。
基于Java web 实现的仓库管理系统源码,适用于初学者了解Java web的开发过程以及仓库管理系统的实现。
资源名称:Python-文件重命名-自定义添加文字-重命名 类型:windows—exe可执行工具 环境:Windows10或以上系统 功能: 1、点击按钮 "源原文"【浏览】表示:选择重命名的文件夹 2、点击按钮 "保存文件夹"【浏览】表示:保存的路径(为了方便可选择保存在 源文件中 ) 3、功能①:在【头部】添加自定义文字 4、功能②:在【尾部】添加自定义文字 5、功能③:输入源字符 ;输入替换字符 可以将源文件中的字符替换自定义的 6、功能④:自动加上编号_1 _2 _3 优点: 1、非常快的速度! 2、已打包—双击即用!无需安装! 3、自带GUI界面方便使用!
JDK8安装包
配合作者 一同使用 作者地址没有次下载路径 https://blog.csdn.net/weixin_52372189/article/details/127471149?fromshare=blogdetail&sharetype=blogdetail&sharerId=127471149&sharerefer=PC&sharesource=weixin_45375332&sharefrom=from_link
GEE训练教程
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
基于springboot交通感知与车路协同系统源码数据库文档.zip
基于springboot+vue 雅妮电影票购买系统源码数据库文档.zip
为了更好地理解 HTML5 的拖放功能,我们设计了一个简单有趣的示例:将水果从水果区拖放到购物笼中,实时更新数量和价格,并在所有水果被成功放置后,播放音效并显示提示。
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。