Security权限框架技术

网站常见错误之503错误解决方法
January 4, 2015
WordPress 添加Meta Box
December 26, 2016

Security权限框架技术

 1.Security介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inverse of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security对Web安全性的支持大量地依赖于Filter过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截请求,并将这些请求转给认证和访问决策管理器处理, 从而增强安全性。根据自己的需要,可以使用所列的几个过滤器来保护自己的应用程序是非侵入式安全架构
基于Servelt Filter和Spring AOP,使商业逻辑与安全逻辑分开,结构更清晰,使用Spring来代理对象, 能方便得保护方法调用

1.1.2.搭建Security开发环境

加载相关.JAR包

Web.xml基本配置
<!–
其实就是一个普通的过滤器,与Security无关,主要是用来获取Spring配置文件,和一些缺省配置的
SpringSecurityFilterChain 则默认会加载安全验证缺省的11个过滤器 XML中配置<bebug>标签可见
–>

[cce]
<p><filter>
 <filter-name>springSecurityFilterChain</filter-name>
 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>
 <!-- 所有请求都需要经过Security验证 -->
 <filter-mapping>
 <filter-name>springSecurityFilterChain</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>
 <!-- 用来加载Spring的配置文件 -->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:applicationContext-*.xml</param-value>
 </context-param>
 <listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 配置Spring-security-3.2.xsd
 <bean:beans xmlns:bean="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://www.springframework.org/schema/security"
 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.2.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 </bean:beans>
 注意:相应的文件可以在spring-security-config-3.2.0.M2.jar 中org.springframework.security.config来获取, 注意,由于3.0、3.1、3.2的Security都有差别,语法也不相同, 在指定 xsd的版本一定要与导入包的版本一致</p><h4>1.1.3.配置基本用户与角色信息</h4>
[/cce]

<!–
配置用户、密码、相应的角色信息,以后的数据从数据库中加载
默认的id值为:org.springframework.security.authenticationManager且不建议修改(修改启动报错),
因为<http/>元素默认引用它。但我们可以给它取一个别名即alias=”authenticationManager”
–>

[cce]
<authentication-manager>
 <authentication-provider>
 <!-- 配置用户和角色的信息 ,authorities:角色名称-->
 <user-service>
 <user name="admin" authorities="ROLE_ADMIN" password="admin"/>
 <user name="user" authorities="ROLE_USER" password="user"/>
 </user-service>
 </authentication-provider>
 </authentication-manager>
[/cce]

1.1.4.配置角色与权限的相关信息

[cce]
<!-- 配置角色与权限的关系 auto-config: true: 启动默认配置,会有注册、退出、和校验的基本功能
 pattern: URL的地址,支持匹配符 access: 哪些角色可以访问URL资源
 use-expressions:true 则access: 支持表达式, 其实是调用了 WebSecurityExpressionRoot 里面的: hasRole hasAnyRole
 -->
 <http auto-config="true" use-expressions="true">
 <intercept-url pattern="/admin/**" access="hasAnyRole('ROLE_ADMIN')"/>
 <intercept-url pattern="/user/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
 </http>
 <!-- 设置debug模式,可以在控制台中查看过滤的相关信息 -->
 <debug/>
[/cce]

通过上面的配置我们可以测试, 最基本的Spring Security权限控制了. 这是Spring的最小化配置, 并且启动的时候默认加载了11个过滤器来实现这些操作. 接下来我们要查看相应的源码. 和过滤器.知道基本的运行逻辑

1.1.5.DelegatingFilterProxy源码分析

此类与Spring安全验证无关,是用来通过查找Spring的配置文件,创建Security需要的过滤器的工具类,
initFilterBean在父类GenericFilterBean中定义, 而且在父类init()方法中执行,主要是用来创建Security主过滤器的, 如果没有则默认根据SpringSecurityFilterChain来创建缺省的过滤链,当然也可以手动配置过滤链
@Override

[cce]
<p>protected void initFilterBean() throws ServletException {
 if (this.targetBeanName == null) {
 this.targetBeanName = getFilterName();
 }
 synchronized (this.delegateMonitor){
 WebApplicationContext wac = findWebApplicationContext();
 if (wac != null) {
 this.delegate = initDelegate(wac);
 }
 }
 }</p><p>public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
 throws ServletException, IOException {
 // 查找Spring的配置文件
 WebApplicationContext wac = findWebApplicationContext();
 if (wac == null) { // 如果没有则抛出异常
 throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
 }
 this.delegate = initDelegate(wac);
 }
 delegateToUse = this.delegate;
 }
 // delegateToUse 来完成过滤的操作
 invokeDelegate(delegateToUse, request, response, filterChain);
 }</p>
[/cce]

1.1.6.分析默认的登录页面

配置完成上面的程序后,就可以运行测试了。以下是运行的截图:/spring_security_login Spring security提供的默认登录页面:此页面只是为了演示最小化配置, 后面我们会自己设计登录页面

此页面是通过过滤器DefaultLoginPageGeneratingFilter生成的,生成的JSP代码如下

[cce]
<html>
 <head><title>Login Page</title></head><body onload='document.f.j_username.focus();'>
 <h3>Login with Username and Password</h3><form name='f' action='/security/j_spring_security_check' method='POST'>
 <table>
 <tr><td>User:</td><td><input type='text' name='j_username' value=''></td></tr>
 <tr><td>Password:</td><td><input type='password' name='j_password'/></td></tr>
 <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
 </table>
 </form>
 </body>
 </html>
[/cce]

1.2优化登录配置

1.2.1.修改登录页面外观

[cce]
<http auto-config="true" use-expressions="true" access-denied-page="/login.jsp?error=access-denied-page">
 <intercept-url pattern="/admin/**" access="hasAnyRole('ROLE_ADMIN')"/>
 <intercept-url pattern="/user/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
 <!-- 自定义登录页面, default-target-url:登录成功缺省目标页面, login-page:登录页面
 authentication-failure-url 登录失败指定的页面-->
 <form-login default-target-url="/index.jsp"
 login-page="/login.jsp"
 authentication-failure-url="/login.jsp?error=authentication-failure-url"
 username-parameter="username"
 password-parameter="password"/>
 </http>
[/cce]

1.2.2.SecurityContextPersistenceFilte分析

[cce]
SecurityContextPersistenceFilter:通过观察Filter的名字,就可以大概才猜出来这个过滤器的作用, 持久化SecuntyContext实例
 此过滤器的位置为: org.springframework.security.web.context.SecurityContextPersistenceFilter
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
 throws IOException, ServletException {
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletRes ponse response = (HttpServletResponse) res;
 // 确定一个请求此过滤器执行一次
 if (request.getAttribute(FILTER_APPLIED) != null) {
 chain.doFilter(request, response);
 return;
 }
 final boolean debug = logger.isDebugEnabled();
 // 给Filter_AppLIED设置一个标记值,注意标记是存储在request中的,不同的请求request不一样
 request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
 if (forceEagerSessionCreation) {
 HttpSession session = request.getSession();
 if (debug && session.isNew()) {
 logger.debug("Eagerly created session: " + session.getId());
 }
 }
 // 封装rquest,response参数
 HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
 // 创建一个空白的SecurityContext对象
 SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
 try {
 // SecurityContext存储到SecurityContextHolder中
 SecurityContextHolder.setContext(contextBeforeChainExecution);
 // 执行下一个过滤器, 把request,response信息交给下一个过滤器
 chain.doFilter(holder.getRequest(), holder.getResponse());
 } finally {
 // 获取SecurityContext, 与前面的SecurityContextHolder.setContext对应
 SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
 // Crucial removal of SecurityContextHolder contents - do this before anything else.
 SecurityContextHolder.clearContext();
 // 把SecurityContext保存到session中,名称为 SPRING_SECURITY_CONTEXT
 repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
 // 移除FILTER_APPLIED标记,
 request.removeAttribute(FILTER_APPLIED);
 if (debug) {
 logger.debug("SecurityContextHolder now cleared, as request processing completed");
 }
 }
 }
[/cce]

1.2.3.登录成功后获取登录信息

[cce]
public void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 Principal userPrincipal = request.getUserPrincipal();
 System.out.println(userPrincipal.getName());
 System.out.println("-----获取相关的的登录信息-------");
 SecurityContext securityContext=(SecurityContext)request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
 System.out.println(securityContext);
 User user=(User)securityContext.getAuthentication().getPrincipal();
 System.out.println("用户名:"+ user.getUsername() +",密码:"+user.getPassword()+",角色:" + user.getAuthorities());
 System.out.println("-----也可以使用工具类获取-------");
 securityContext = SecurityContextHolder.getContext();
 user=(User)securityContext.getAuthentication().getPrincipal();
 System.out.println("用户名:"+ user.getUsername() +",密码:"+user.getPassword()+",角色:" + user.getAuthorities());
 }
[/cce]

思考,如果登录成功要在页面显示, 用户的信息怎么办, 这些代码, 如果直接写在页面很明显是不行的, 所以可以创建一个属性监听器,来实现类似的功能, 监听器要配置到web.xml, 而且注意, 每次测试要重新打开浏览器, 向相同的属性中添加不会触发attributeAdded

1.2.4.HttpSessionAttributeListener优化登录信息

[cce]
<p>public class SecuritySessionAttributeListener implements HttpSessionAttributeListener {
 @Override
 public void attributeAdded(HttpSessionBindingEvent event) {
 // 判断新添加的数据是否为SPRING_SECURITY_CONTEXT
 if(event.getName().equals("SPRING_SECURITY_CONTEXT")){
 SecurityContext context = SecurityContextHolder.getContext();
 User user = (User)context.getAuthentication().getPrincipal();
 event.getSession().setAttribute("user", user);
 }
 }
 @Override
 public void attributeRemoved(HttpSessionBindingEvent event) {</p><p>}
 @Override
 public void attributeReplaced(HttpSessionBindingEvent event) {
 }
 }</p>
[/cce]

Leave a Reply

Your email address will not be published. Required fields are marked *