乐清高端网站建设,软文推广300字,家政服务技术支持东莞网站建设,企业网站备案网站一、SpringSecurity
1.1 什么是SpringSecurity
Spring Security 的前身是 Acegi Security #xff0c;是 Spring 项目组中用来提供安全认证服务的框架。(官网地址) Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发…一、SpringSecurity
1.1 什么是SpringSecurity
Spring Security 的前身是 Acegi Security 是 Spring 项目组中用来提供安全认证服务的框架。(官网地址) Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。人们使用Spring Security有很多种原因不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。特别要指出的是他们不能再
WAR 或 EAR 级别进行移植。这样如果你更换服务器环境就要在新的目标环境进行大量的工作对你的应用
系统进行重新配置安全。使用Spring Security 解决了这些问题也为你提供很多有用的完全可以指定的其他安
全特性。安全包括两个主要操作。
“认证”是为用户建立一个他所声明的主体。主体一般是指用户设备或可以在你系统中执行动作的其他系
统。可以将主体当前权限框架自己的session认证其实就是登录操作并将登录成功的数据信息存入主体
“授权”指的是一个用户能否在你的应用中执行某个操作在到达授权判断之前身份的主题已经由身份验证
过程建立了。查询是否对应权限授权其实就是在认证之后请求需要权限的资源时查询数据库在主体中保存对应权限数据
这些概念是通用的不是Spring Security特有的。在身份验证层面Spring Security广泛支持各种身份验证模式
这些验证模型绝大多数都由第三方提供或则正在开发的有关标准机构提供的例如 Internet Engineering Task
Force.作为补充Spring Security 也提供了自己的一套验证功能。
Spring Security 目前支持认证一体化如下认证技术
HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)
HTTP Digest authentication headers (一个基于IEFT RFC 的标准)
HTTP X.509 client certi?cate exchange(一个基于IEFT RFC 的标准)
LDAP (一个非常常见的跨平台认证需要做法特别是在大环境)
Form-based-authentication (提供简单用户接口的需求)
OpenID authentication Computer Associates Siteminder JA-SIG Central Authentication Service (CAS这是一个流行的开源单点登录系统)
Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)
1.2 核心组件
SecurityContextHolder
SecurityContextHolder它持有的是安全上下文 security context的信息。当前操作的用户是谁该用户是否已经被认证他拥有哪些角色权等等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认使用ThreadLocal 策略来存储认证信息。看到 ThreadLocal 也就意味着这是一种与线程绑定的策略。在web环境下Spring Security在用户登录时自动绑定认证信息到当前线程在用户退出时自动清除当前线程的认证信息
看源码他有静态方法
//获取 上下文public static SecurityContext getContext() {return strategy.getContext();}//清除上下文 public static void clearContext() {strategy.clearContext();}SecurityContextHolder.getContext().getAuthentication().getPrincipal() getAuthentication() 返回了认证信息
getPrincipal() 返回了身份信息
UserDetails 便是Spring对身份信息封装的一个接口
SecurityContext
安全上下文主要持有 Authentication 对象如果用户未鉴权那Authentication对象将会是空的。看源码可知
package org.springframework.security.core.context;import java.io.Serializable;
import org.springframework.security.core.Authentication;public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication var1);
}Authentication
鉴权对象该对象主要包含了用户的详细信息 UserDetails 和用户鉴权时所需要的信息如用户提交的用户名密码、Remember-me Token或者digest hash值等按不同鉴权方式使用不同的 Authentication 实现看源码可知道
package org.springframework.security.core;import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;public interface Authentication extends Principal, Serializable {Collection? extends GrantedAuthority getAuthorities();Object getCredentials();Object getDetails();Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean var1) throws IllegalArgumentException;}注意 GrantedAuthority 该接口表示了当前用户所拥有的权限或者角色信息。这些信息由授权负责对象 AccessDecisionManager 来使用并决定最终用户是否可以访问某 资源URL或方法调用或域对象。鉴权时并不会使用到该对象
UserDetails
这个接口规范了用户详细信息所拥有的字段譬如用户名、密码、账号是否过期、是否锁定等。在Spring Security中获取当前登录的用户的信息,一般情况是需要在这个接口上面进行 扩展用来对接自己系统的用户看源码可知
package org.springframework.security.core.userdetails;import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;public interface UserDetails extends Serializable {Collection? extends GrantedAuthority getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}UserDetailsService
这个接口只提供一个接口 loadUserByUsername(String username) 这个接口非常 重要 一般情况我们都是通过 扩展 这个接口来显示获取我们的用户信息用户登陆时传递的用户名和密码也是通过这里这查找出来的用户名和密码进行校验但是真正的校验不在这里而是由 AuthenticationManager 以及 AuthenticationProvider 负责的需要强调的是如果用户不存在不应返回 NULL而要抛出异常 UsernameNotFoundException看源码可知
package org.springframework.security.core.userdetails;public interface UserDetailsService {UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}1.3 SpringSecurity快速入门
创建测试web工程 导入依赖
dependencies!--springMVC--dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.2.9.RELEASE/version/dependency!--servlet--dependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion4.0.1/versionscopeprovided/scope/dependency!--jsp--dependencygroupIdjavax.servlet/groupIdartifactIdjsp-api/artifactIdversion2.0/versionscopeprovided/scope/dependency!--jstl--dependencygroupIdjstl/groupIdartifactIdjstl/artifactIdversion1.2/version/dependency!--jsckson --dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.9.9/version/dependency!--文件上传--dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.4/version/dependency!--spring相关坐标--dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.5/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-jdbc/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-test/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-web/artifactIdversion5.2.9.RELEASE/version/dependency!--mybatis相关坐标--dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.47/version/dependency!--数据库连接池--dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.1.15/version/dependency!--mybatis--dependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.6/version/dependency!--spring-mybatis坐标--dependencygroupIdorg.mybatis/groupIdartifactIdmybatis-spring/artifactIdversion1.3.2/version/dependency!--lombok--dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.10/version/dependency!--分页插件--dependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper/artifactIdversion5.1.10/version/dependency!--junit单元测试--dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version/dependency!--spring-security--dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-web/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-config/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-core/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-taglibs/artifactIdversion5.2.9.RELEASE/version/dependencydependencygroupIdjavax.annotation/groupIdartifactIdjsr250-api/artifactIdversion1.0/version/dependency!--log4j依赖--dependencygroupIdlog4j/groupIdartifactIdlog4j/artifactIdversion1.2.17/version/dependency/dependenciesweb.xml
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://java.sun.com/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsdversion3.0context-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-security.xml/param-value/context-paramlistenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listenerfilterfilter-namespringSecurityFilterChain/filter-namefilter-classorg.springframework.web.filter.DelegatingFilterProxy/filter-class/filterfilter-mappingfilter-namespringSecurityFilterChain/filter-nameurl-pattern/*/url-pattern/filter-mappingwelcome-file-listwelcome-fileindex.html/welcome-filewelcome-fileindex.htm/welcome-filewelcome-fileindex.jsp/welcome-filewelcome-filedefault.html/welcome-filewelcome-filedefault.htm/welcome-filewelcome-filedefault.jsp/welcome-file/welcome-file-list
/web-appspring-security.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:securityhttp://www.springframework.org/schema/securityxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsdsecurity:http auto-configtrue use-expressionsfalse !--intercept-url定义一个过滤规则pattern表示对哪些url进行权限控制ccess属性表示在请求对应的URL时需要什么权限默认配置时它应该是一个以逗号分隔的角色列表请求的用户只需拥有其中的一个角色就能成功访问对应的URL--security:intercept-url pattern/** accessROLE_USER /!--security:form-login /--/security:httpsecurity:authentication-managersecurity:authentication-providersecurity:user-servicesecurity:user nameuser password{noop}userauthoritiesROLE_USER /security:user nameadmin password{noop}adminauthoritiesROLE_ADMIN //security:user-service/security:authentication-provider/security:authentication-manager
/beans测试 1.4 SpringSecurity 使用自定义页面
添加以下页面 修改SpringSecurity.xml配置文件 !-- 配置不过滤的资源静态资源及登录相关 --
security:http securitynone pattern/login.html /
security:http securitynone pattern/failer.html /
security:http auto-configtrue use-expressionsfalse !-- 配置资料连接表示任意路径都需要ROLE_USER权限 --security:intercept-url pattern/** accessROLE_USER /!-- 自定义登陆页面login-page 自定义登陆页面 authentication-failure-url 用户权限校验失败之后才会跳转到这个页面如果数据库中没有这个用户则不会跳转到这个页面。default-target-url 登陆成功后跳转的页面。 注登陆页面用户名固定 username密码 passwordaction:login --security:form-login login-page/login.htmllogin-processing-url/login username-parameterusernamepassword-parameterpassword authentication-failure-url/failer.htmldefault-target-url/success.html authentication-success-forward-url/success.html/!-- 关闭CSRF,默认是开启的 --security:csrf disabledtrue /
/security:http
security:authentication-managersecurity:authentication-providersecurity:user-servicesecurity:user nameuser password{noop}userauthoritiesROLE_USER /security:user nameadmin password{noop}adminauthoritiesROLE_ADMIN //security:user-service/security:authentication-provider/security:authentication-manager二、 用户登录
2.1 表结构分析与创建 -- 用户表
CREATE TABLE users(id VARCHAR(32) PRIMARY KEY,email VARCHAR(50) UNIQUE NOT NULL,username VARCHAR(50),PASSWORD VARCHAR(100),phoneNum VARCHAR(20),STATUS INT
);-- 角色表
CREATE TABLE role(id VARCHAR(32) PRIMARY KEY,roleName VARCHAR(50) ,roleDesc VARCHAR(50)
);-- 用户角色关联表
CREATE TABLE users_role(userId VARCHAR(32),roleId VARCHAR(32),PRIMARY KEY(userId,roleId),FOREIGN KEY (userId) REFERENCES users(id),FOREIGN KEY (roleId) REFERENCES role(id)
);-- 资源权限表
CREATE TABLE permission(id VARCHAR(32) PRIMARY KEY,permissionName VARCHAR(50) ,url VARCHAR(50)
);-- 角色权限关联表
CREATE TABLE role_permission(permissionId VARCHAR(32),roleId VARCHAR(32),PRIMARY KEY(permissionId,roleId),FOREIGN KEY (permissionId) REFERENCES permission(id),FOREIGN KEY (roleId) REFERENCES role(id)
);2.2 创建实体类
创建Users
Data
NoArgsConstructor
public class Users{private int id;private String email;private String username;private String password;private String phoneNum;private int status;// 状态0 未开启 1 开启private String statusStr;private Role role;public String getStatusStr() {if(status0){statusStr未开启;}else if(status1){statusStr开启;}return statusStr;}
}创建Role
Data
NoArgsConstructor
public class Role {private int id;private String roleName;private String roleDesc;private ListPermission permissions;
}创建Permission
Data
NoArgsConstructor
public class Permission {private int id;private String permissionName;private String url;
}
2.3 Spring Security使用数据库认证
在Spring Security中如果想要使用数据进行认证操作有很多种操作方式这里我们介绍使用UserDetails、
UserDetailsService来完成操作。 UserDetails public interface UserDetails extends Serializable {Collection? extends GrantedAuthority getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}UserDetails是一个接口我们可以认为UserDetails作用是于封装当前进行认证的用户信息但由于其是一个 接口所以我们可以对其进行实现也可以使用Spring Security提供的一个UserDetails的实现类User来完成 操作 以下是User类的部分代码 public class User implements UserDetails, CredentialsContainer {private String password;private final String username;private final SetGrantedAuthority authorities;private final boolean accountNonExpired; //帐户是否过期private final boolean accountNonLocked; //帐户是否锁定private final boolean credentialsNonExpired; //认证是否过期 private final boolean enabled; //帐户是否可用 UserDetailsService public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
} 上面将UserDetails与UserDetailsService做了一个简单的介绍那么我们具体如何完成Spring Security的数据库认证操作哪我们通过用户管理中用户登录来完成Spring Security的认证操作。
2.4 用户登录流程分析 2.5 代码编写
编写login和failer页面 导入spring security相关坐标
!--spring-security--
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-web/artifactIdversion5.2.9.RELEASE/version
/dependency
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-config/artifactIdversion5.2.9.RELEASE/version
/dependency
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-core/artifactIdversion5.2.9.RELEASE/version
/dependency
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-taglibs/artifactIdversion5.2.9.RELEASE/version
/dependency
配置web.xml
!--加载spring环境 和 spring-security配置文件--context-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring/applicationContext*.xml,classpath:spring/spring-security.xml/param-value/context-param!-- 额外添加springSecurity过滤器 --
filterfilter-namespringSecurityFilterChain/filter-namefilter-classorg.springframework.web.filter.DelegatingFilterProxy/filter-class
/filter
filter-mappingfilter-namespringSecurityFilterChain/filter-nameurl-pattern/*/url-pattern
/filter-mapping
配置spring-security.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:securityhttp://www.springframework.org/schema/securityxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd!-- 配置不拦截的页面与静态资源 --security:http pattern/login.jsp securitynone/security:http pattern/failer.jsp securitynone/security:http pattern/css/** securitynone/security:http pattern/img/** securitynone/security:http pattern/layui/** securitynone/!--配置具体的规则auto-configtrue 不用自己编写登录的页面框架提供默认登录页面use-expressionsfalse 是否使用SPEL表达式--security:http auto-configtrue use-expressionsfalse!-- 同源策略 如果页面使用iframe需要配置 否则不能使用 --security:headerssecurity:frame-options disabledtrue//security:headers!-- 配置具体的拦截的规则 pattern请求路径的规则 access访问系统的人必须有ROLE_USER的角色 --security:intercept-url pattern/** accessROLE_USER,ROLE_ADMIN /!-- 定义跳转的具体的页面 --security:form-loginlogin-page/login.jsplogin-processing-url/logindefault-target-url/index.jspauthentication-failure-url/failer.jspauthentication-success-forward-url/index.jsp/!-- 关闭跨域请求 --security:csrf disabledtrue/!-- 退出 --security:logout invalidate-sessiontrue logout-url/logout logout-success-url/login.jsp //security:http!-- 切换成数据库中的用户名和密码 --security:authentication-manager!-- user-service-refuserService springSecurity用于获取账号信息的类 需要实现UserDetailsService 重写加载数据方法 --security:authentication-provider user-service-refuserService!-- 配置加密的方式 注意如果使用需要开启下面配置的加密类security:password-encoder refpasswordEncoder/--/security:authentication-provider/security:authentication-manager!-- 配置加密类 --!--bean idpasswordEncoder classorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder/--!-- 提供了入门的方式在内存中存入用户名和密码security:authentication-managersecurity:authentication-providersecurity:user-servicesecurity:user nameadmin password{noop}admin authoritiesROLE_USER//security:user-service/security:authentication-provider/security:authentication-manager--/beansspring security接收到用户名之后spring security怎么就知道我们要调用的是那个service来完成用户的查询操作呢
在配置文件中有这么一段配置
security:authentication-managersecurity:authentication-provider user-service-refuserService!-- 配置加密的方式security:password-encoder refpasswordEncoder/--/security:authentication-provider
/security:authentication-manageruser-service-ref属性就是来指定要执行的service当然这个service来继承 UserDetailsService扩展我们自己的service接口完成用户的认证的操作
编写UserMapper
public interface UserMapper {//使用权限框架操作 根据账号查询信息Select(select * from users where username#{username} and status1)public Users selectByUserName(String username);}编写UserService
public interface UserService extends UserDetailsService {
}Service(userService)
public class UserServiceImpl implements UserService {AutowiredUserMapper userMapper;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询数据返回实体类对应数据Users users userMapper.selectByUserName(username);if(users!null){//创建springSecurity主体中存储的账号对象并返回(账号,密码权限列表)//权限列表这里填null注意UserDetails userDetailsnew User(users.getUsername(),users.getPassword(),null);return userDetails;}return null;}}测试
在登录页面输入用户名 密码 跳转到了错误页面 三、 用户登录问题分析
问题一登录失败的问题
在数据库中是有jack这个用户的 为什么还是没办法登录原因是在于我们的spring-security.xml配置文件中配置的问题
在配置文件中我们配置了
!-- 配置具体的拦截的规则 pattern请求路径的规则 access访问系统的人必须有ROLE_USER的角色 --
security:intercept-url pattern/** accessROLE_USER,ROLE_ADMIN/spring security 拦截了所有的请求要想登录认证必须是有ROLE_USER,ROLE_ADMIN两个权限的在UserServiceImpl类中我将查询到的UserInfo中的信息封装到了User对象中有一个值是为null User user new User(userInfo.getUsername(),userInfo.getPassword(), null);也就是说我们查询出的这个对象是没有权限的既是用户名和密码对了没有权限也是没有办法登录的
在这里我们需要模拟给出ROLE_USER,ROLE_ADMIN两个权限修改UserServiceImpl类
Service(userService)
public class UserServiceImpl implements UserService {AutowiredUserMapper userMapper;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询数据返回实体类对应数据Users users userMapper.selectByUserName(username);if(users!null){//创建springSecurity主体中存储的账号对象并返回(账号,密码权限列表)UserDetails userDetailsnew User(users.getUsername(),users.getPassword(),getAuthority());return userDetails;}return null;}public ListSimpleGrantedAuthority getAuthority(){ArrayListSimpleGrantedAuthority simpleGrantedAuthorities new ArrayList();simpleGrantedAuthorities.add(new SimpleGrantedAuthority(ROLE_USER));simpleGrantedAuthorities.add(new SimpleGrantedAuthority(ROLE_ADMIN));return simpleGrantedAuthorities;}
}修改完毕之后我们再次测试看是否能登录成功
这个报错的原因是spring security 默认使用的是密文提交的现在没有进行加密 我们只需要在获取密码的前面加上{noop} 代表的是使用明文 此时就认证成功
问题二数据库查询用户角色
在上面的代码中我们的用户角色是自己手动添加的这不是通用的一种方式用户的角色我们要从数据库中进行查询
修改UserMapper.xml
?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtdmapper namespacecn.yanqi.ssm.mapper.UserMapper!--通过用户名查询用户--resultMap iduserResultMap typeUserInfo autoMappingtrueid propertyid columnid /collection propertyroles ofTypeRole javaTypeList autoMappingtrueid propertyid columnrid //collection/resultMapselect idfindUserByUserName resultMapuserResultMapSELECT* ,r.id ridFROMusers u,role r,users_role urWHEREu.id ur.userId ANDr.id ur.roleId ANDu.username #{username}/select/mapper修改UserService
在这里需要注意的是获取到对应的角色之后其实还是不能登录在users表中有一个字段status这个字段表示的用户是否可能0表示可用 1表示不可用。此时的功能原来使用的构造已经无法满足了我们需要使用另外一个构造 public User(String username, String password,/**boolean enabled: 账户是否可用boolean accountNonExpired账户是否过期boolean credentialsNonExpired密码是否过期boolean accountNonLocked账户是否冻结被锁定authorities该账户所具有的权限*/boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection? extends GrantedAuthority authorities) {}完整代码实现
Service(userService)
public class UserServiceImpl implements UserService {Autowiredprivate UserMapper userMapper;Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {UserInfo userInfo userMapper.findUserByUserName(s);User user new User(userInfo.getUsername(),{noop}userInfo.getPassword(),userInfo.getStatus() 0 ? false : true ,true,true,true,getAuthority(userInfo.getRoles()));return user;}public ListSimpleGrantedAuthority getAuthority(ListRole roles){ArrayListSimpleGrantedAuthority simpleGrantedAuthorities new ArrayList();for (Role role : roles) {simpleGrantedAuthorities.add(new SimpleGrantedAuthority(ROLE_role.getRoleName()));}return simpleGrantedAuthorities;}
}
四、动态权限 在实际开发中一般通过对数据库表的操作实现动态的权限操作SpringSecurity实现动态权限比较复杂需要自己实现过滤器与决策器所以这里的内容是基于权限管理crud功能实现后的配置没有进行详细的讲解如需详细了解可以查看Spring Security认证与授权的原理 4.1 将公开权限设置为无需认证即可访问 !-- 配置不拦截的资源 --security:http pattern/login.jsp securitynone/security:http pattern/failer.jsp securitynone/security:http pattern/css/** securitynone/security:http pattern/img/** securitynone/security:http pattern/layui/** securitynone/4.2 配置具体的规则 !--配置具体的规则auto-configtrue 不用自己编写登录的页面框架提供默认登录页面use-expressionstrue 是否使用SPEL表达式否则只能使用USER_角色的形式配置--security:http auto-configtrue use-expressionstrue!-- 同源策略 如果页面使用iframe需要配置 否则不能使用 --security:headerssecurity:frame-options disabledtrue//security:headers!-- 定义跳转的具体的页面 --security:form-loginlogin-page/login.jsplogin-processing-url/logindefault-target-url/index.jspauthentication-failure-url/failer.jspauthentication-success-forward-url/users/name/security:intercept-url pattern/** accessisAuthenticated()/!-- 配置具体的拦截的规则 pattern请求路径的规则 accessisAuthenticated() !-- 权限框架本质是过滤器链 会依次进行权限验证 如果验证通过继续执行 该配置 配置的是 除以上不拦截的资源外 所有url请求必须拥有认证的权限登录后才能访问
--!-- 关闭跨域请求 --security:csrf disabledtrue/!-- 退出 --security:logout invalidate-sessiontrue logout-url/logout logout-success-url/login.jsp //security:http2.3 设置权限数据 4.4 自定义相关过滤器与决策器
自定义决策管理器
权限框架本身是由多个不同功能的过滤器组成的不同的过滤器负责不同的功能例如认证过滤、静态资源过滤、等 授权过滤也是一样决策器就是同于判断当前请求的url当前账号是否拥有权限
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;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Iterator;
Service
//决策管理器
public class MyAccessDecisionManager implements AccessDecisionManager {// decide 方法是判定是否拥有权限的决策方法//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.//object 包含客户端发起的请求的requset信息可转换为 HttpServletRequest request ((FilterInvocation) object).getHttpRequest();//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果此方法是为了判定用户请求的url 是否在权限表中如果在权限表中则返回给 decide 方法用来判定用户是否有此权限。如果不在权限表中则放行。Overridepublic void decide(Authentication authentication, Object object, CollectionConfigAttribute configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {if(null configAttributes || configAttributes.size() 0) {return;}ConfigAttribute c;String needRole;for(IteratorConfigAttribute iter configAttributes.iterator(); iter.hasNext(); ) {c iter.next();needRole c.getAttribute();for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合if(needRole.trim().equals(ga.getAuthority())) {return;}}}throw new AccessDeniedException(no right);}Overridepublic boolean supports(ConfigAttribute attribute) {return true;}Overridepublic boolean supports(Class? clazz) {return true;}
}自定义权限加载器
import com.yunhe.javabean.Permission;
import com.yunhe.mapper.PermissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import java.util.*;Service
//授权管理器
//用于当前项目要动态配置的权限信息
//从数据库中取出所有的权限信息 进行配置
//这样当客户请求对应权限时进行权限验证因为不可能将所有的url都过滤
public class MyInvocationSecurityMetadataSourceService implementsFilterInvocationSecurityMetadataSource {Autowired//注入权限查询的dao层private PermissionMapper permissionMapper;private HashMapString, CollectionConfigAttribute map null;/*** 加载权限表中所有权限*/public void loadResourceDefine(){map new HashMap();CollectionConfigAttribute array;ConfigAttribute cfg;//动态查询当前数据库中所有的权限ListPermission permissions permissionMapper.selectAll();for(Permission permission : permissions) {array new ArrayList();cfg new SecurityConfig(permission.getPermissionName());//此处只添加了权限的名字其实还可以添加更多权限的信息例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。array.add(cfg);//用权限的getUrl() 作为map的key用ConfigAttribute的集合作为 value//实际加载存储的结构为 url-[name1,name2....]//url是进行请求url拦截使用的 name是进行权限验证使用的//也就是说在用户进行授权时 实际加载的是权限名称map.put(permission.getUrl(), array);}}//此方法是为了判定用户请求的url 是否在权限表中如果在权限表中则返回给 decide 方法用来判定用户是否有此权限。如果不在权限表中则放行。Overridepublic CollectionConfigAttribute getAttributes(Object object) throws IllegalArgumentException {if(map null) loadResourceDefine();//object 中包含用户请求的request 信息HttpServletRequest request ((FilterInvocation) object).getHttpRequest();AntPathRequestMatcher matcher;String resUrl;for(IteratorString iter map.keySet().iterator(); iter.hasNext(); ) {resUrl iter.next();matcher new AntPathRequestMatcher(resUrl);if(matcher.matches(request)) {return map.get(resUrl);}}return null;}Overridepublic CollectionConfigAttribute getAllConfigAttributes() {return null;}Overridepublic boolean supports(Class? clazz) {return true;}
}自定义权限拦截器
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.stereotype.Service;import javax.servlet.*;
import java.io.IOException;Service
//自定义权限拦截器
//FilterSecurityInterceptor是权限框架中用于处理权限验证的过滤器
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {//使用自己定义权限加载器Autowiredprivate FilterInvocationSecurityMetadataSource securityMetadataSource;//使用自定义的决策管理器Autowiredpublic void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {super.setAccessDecisionManager(myAccessDecisionManager);}Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic 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) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够InterceptorStatusToken token super.beforeInvocation(fi);try {
//执行下一个拦截器fi.getChain().doFilter(fi.getRequest(), fi.getResponse());} finally {super.afterInvocation(token, null);}}Overridepublic void destroy() {}Overridepublic Class? getSecureObjectClass() {return FilterInvocation.class;}Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return this.securityMetadataSource;}
}4.5 将自定义权限拦截器配置入权限框架中
在security:http标签中添加 !-- 配置自定义拦截器 将定义拦截器配置到当前权限框架拦截器链中的指定位置将其配置在本身的权限认证过滤器之后执行--
security:custom-filter refmyFilterSecurityInterceptor afterFILTER_SECURITY_INTERCEPTOR/security:custom-filter 4.6 配置授权页面
在配置后使用账号登录会出现没有权限403代码页面 如果用户登录进行操作时直接报403错误用户体检并不好我们可以制作一个比较好看的页面告诉用户您用户不足请联系管理员
在web.xml中配置 error-pageerror-code403/error-codelocation/failer.jsp/location/error-page4.7 授权
在书写权限加载器时我们发现加载器加载url是用于http请求的过滤匹配而实际进行权限验证使用的是权限名称为了方便书写我们在登录认证时查询权限信息进行授权操作
修改PermissionMapper //根据用户id查询权限数据public ListPermission selectByUid(int uid);?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtdmapper namespacecom.yunhe.mapper.PermissionMapperselect idselectByUid resultTypecom.yunhe.javabean.Permissionselect p.*from users u,role r,permission p,users_role ur,role_permission rpwhere u.idur.userId and r.idur.roleId and r.idrp.roleId and p.id rp.permissionId and u.id#{uid}/select
/mapper修改PermissionService //根据uid查询权限数据public ListPermission findByUid(int uid);Overridepublic ListPermission findByUid(int uid) {return permissionMapper.selectByUid(uid);}修改userService
将我们之前书写写死的角色认证与权限认证查询数据库的形式添加 Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询数据返回实体类对应数据Users users userMapper.selectByUserName(username);if (users ! null) {//创建springSecurity主体中存储的账号对象并返回(账号,密码权限列表)UserDetails userDetails new User(users.getUsername(), users.getPassword(), getAuthority(users.getId()));return userDetails;}return null;}AutowiredPermissionService permissionService;//获取权限列表public ListGrantedAuthority getAuthority(int uid) {ListGrantedAuthority grantedAuthorities new ArrayList();//查询指定用户权限列表ListPermission permissionList permissionService.findByUid(uid);for (Permission p:permissionList ) {GrantedAuthority grantedAuthority new SimpleGrantedAuthority(p.getPermissionName());grantedAuthorities.add(grantedAuthority);}return grantedAuthorities;}