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远程调用协议)
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对身份信息封装的一个接口
安全上下文,主要持有 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);
}
鉴权对象,该对象主要包含了用户的详细信息 (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或方法调用或域对象)。鉴权时并不会使用到该对象
这个接口规范了用户详细信息所拥有的字段,譬如用户名、密码、账号是否过期、是否锁定等。在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();
}
这个接口只提供一个接口 loadUserByUsername(String username) ,这个接口非常 重要, 一般情况我们都是通过 扩展 这个接口来显示获取我们的用户信息,用户登陆时传递的用户名和密码也是通过这里这查找出来的用户名和密码进行校验,但是真正的校验不在这里,而是由 AuthenticationManager 以及 AuthenticationProvider 负责的,需要强调的是,如果用户不存在,不应返回 NULL,而要抛出异常 UsernameNotFoundException,看源码可知
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>jstlgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.9version>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.15version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.1.10version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-coreartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-taglibsartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>jsr250-apiartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-security.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<welcome-file-list>
<welcome-file>index.htmlwelcome-file>
<welcome-file>index.htmwelcome-file>
<welcome-file>index.jspwelcome-file>
<welcome-file>default.htmlwelcome-file>
<welcome-file>default.htmwelcome-file>
<welcome-file>default.jspwelcome-file>
welcome-file-list>
web-app>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="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.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http auto-config="true" use-expressions="false" >
<security:intercept-url pattern="/**" access="ROLE_USER" />
security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="{noop}user"
authorities="ROLE_USER" />
<security:user name="admin" password="{noop}admin"
authorities="ROLE_ADMIN" />
security:user-service>
security:authentication-provider>
security:authentication-manager>
beans>
<security:http security="none" pattern="/login.html" />
<security:http security="none" pattern="/failer.html" />
<security:http auto-config="true" use-expressions="false" >
<security:intercept-url pattern="/**" access="ROLE_USER" />
<security:form-login login-page="/login.html"
login-processing-url="/login"
username-parameter="username"
password-parameter="password" authentication-failure-url="/failer.html"
default-target-url="/success.html" authentication-success-forward-url="/success.html"
/>
<security:csrf disabled="true" />
security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="{noop}user"
authorities="ROLE_USER" />
<security:user name="admin" password="{noop}admin"
authorities="ROLE_ADMIN" />
security:user-service>
security:authentication-provider>
security:authentication-manager>
-- 用户表
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)
);
@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(status==0){
statusStr="未开启";
}else if(status==1){
statusStr="开启";
}
return statusStr;
}
}
@Data
@NoArgsConstructor
public class Role {
private int id;
private String roleName;
private String roleDesc;
private List<Permission> permissions;
}
@Data
@NoArgsConstructor
public class Permission {
private int id;
private String permissionName;
private String url;
}
在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 Set<GrantedAuthority> 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的认证操作。
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-coreartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-taglibsartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/applicationContext*.xml,classpath:spring/spring-security.xmlparam-value>
context-param>
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="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.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/layui/**" security="none"/>
<security:http auto-config="true" use-expressions="false">
<security:headers>
<security:frame-options disabled="true"/>
security:headers>
<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" />
<security:form-login
login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/index.jsp"
/>
<security:csrf disabled="true"/>
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" />
security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
security:authentication-provider>
security:authentication-manager>
beans>
spring security接收到用户名之后,spring security怎么就知道我们要调用的是那个service来完成用户的查询操作呢?
在配置文件中有这么一段配置:
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
security:authentication-provider>
security:authentication-manager>
user-service-ref
属性就是来指定要执行的service,当然这个service来继承 UserDetailsService,扩展我们自己的service接口,完成用户的认证的操作
public interface UserMapper {
//使用权限框架操作 根据账号查询信息
@Select("select * from users where username=#{username} and status=1")
public Users selectByUserName(String username);
}
public interface UserService extends UserDetailsService {
}
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询数据返回实体类对应数据
Users users = userMapper.selectByUserName(username);
if(users!=null){
//创建springSecurity主体中存储的账号对象并返回(账号,密码,权限列表)
//权限列表这里填null注意
UserDetails userDetails=new User(users.getUsername(),users.getPassword(),null);
return userDetails;
}
return null;
}
}
在登录页面输入用户名 密码 跳转到了错误页面:
在数据库中是有jack这个用户的
为什么还是没办法登录,原因是在于我们的spring-security.xml配置文件中配置的问题:
在配置文件中,我们配置了
<security:intercept-url pattern="/**" access="ROLE_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 {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询数据返回实体类对应数据
Users users = userMapper.selectByUserName(username);
if(users!=null){
//创建springSecurity主体中存储的账号对象并返回(账号,密码,权限列表)
UserDetails userDetails=new User(users.getUsername(),users.getPassword(),getAuthority());
return userDetails;
}
return null;
}
public List<SimpleGrantedAuthority> getAuthority(){
ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return simpleGrantedAuthorities;
}
}
这个报错的原因是spring security 默认使用的是密文提交的,现在没有进行加密! 我们只需要在获取密码的前面加上"{noop}" 代表的是使用明文
此时就认证成功
在上面的代码中我们的用户角色是自己手动添加的,这不是通用的一种方式,用户的角色我们要从数据库中进行查询!
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yanqi.ssm.mapper.UserMapper">
<resultMap id="userResultMap" type="UserInfo" autoMapping="true">
<id property="id" column="id" />
<collection property="roles" ofType="Role" javaType="List" autoMapping="true">
<id property="id" column="rid" />
collection>
resultMap>
<select id="findUserByUserName" resultMap="userResultMap">
SELECT
* ,
r.id rid
FROM
users u,
role r,
users_role ur
WHERE
u.id = ur.userId AND
r.id = ur.roleId AND
u.username = #{username}
select>
mapper>
在这里需要注意的是,获取到对应的角色之后,其实还是不能登录,在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 {
@Autowired
private UserMapper userMapper;
@Override
public 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 List<SimpleGrantedAuthority> getAuthority(List<Role> roles){
ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
for (Role role : roles) {
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
}
return simpleGrantedAuthorities;
}
}
在实际开发中一般通过对数据库表的操作实现动态的权限操作,SpringSecurity实现动态权限比较复杂需要自己实现过滤器与决策器,所以这里的内容是基于权限管理crud功能实现后的配置,没有进行详细的讲解,如需详细了解可以查看Spring Security认证与授权的原理
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/layui/**" security="none"/>
<security:http auto-config="true" use-expressions="true">
<security:headers>
<security:frame-options disabled="true"/>
security:headers>
<security:form-login
login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/users/name"
/>
<security:intercept-url pattern="/**" access="isAuthenticated()"/>
<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="isAuthenticated()"
<security:csrf disabled="true"/>
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" />
security:http>
权限框架本身是由多个不同功能的过滤器组成的,不同的过滤器负责不同的功能例如认证过滤、静态资源过滤、等 授权过滤也是一样,决策器就是同于判断当前请求的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 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute c;
String needRole;
for(Iterator<ConfigAttribute> 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");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public 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 implements
FilterInvocationSecurityMetadataSource {
@Autowired
//注入权限查询的dao层
private PermissionMapper permissionMapper;
private HashMap<String, Collection<ConfigAttribute>> map =null;
/**
* 加载权限表中所有权限
*/
public void loadResourceDefine(){
map = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
//动态查询当前数据库中所有的权限
List<Permission> 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 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if(map ==null) loadResourceDefine();
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public 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 {
//使用自己定义权限加载器
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
//使用自定义的决策管理器
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) 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) 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);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
在security:http标签中添加
<security:custom-filter ref="myFilterSecurityInterceptor" after="FILTER_SECURITY_INTERCEPTOR">security:custom-filter>
在配置后使用账号登录会出现没有权限403代码页面
如果用户登录进行操作时,直接报403错误用户体检并不好,我们可以制作一个比较好看的页面,告诉用户您用户不足,请联系管理员!
在web.xml中配置
<error-page>
<error-code>403error-code>
<location>/failer.jsplocation>
error-page>
在书写权限加载器时,我们发现,加载器加载url是用于http请求的过滤匹配,而实际进行权限验证使用的是权限名称,为了方便书写,我们在登录认证时查询权限信息进行授权操作
修改PermissionMapper
//根据用户id查询权限数据
public List<Permission> selectByUid(int uid);
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yunhe.mapper.PermissionMapper">
<select id="selectByUid" resultType="com.yunhe.javabean.Permission">
select p.*
from users u,role r,permission p,users_role ur,role_permission rp
where u.id=ur.userId and r.id=ur.roleId and r.id=rp.roleId and p.id =rp.permissionId and u.id=#{uid}
select>
mapper>
修改PermissionService
//根据uid查询权限数据
public List<Permission> findByUid(int uid);
@Override
public List<Permission> findByUid(int uid) {
return permissionMapper.selectByUid(uid);
}
修改userService
将我们之前书写写死的角色认证与权限认证查询数据库的形式添加
@Override
public 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;
}
@Autowired
PermissionService permissionService;
//获取权限列表
public List<GrantedAuthority> getAuthority(int uid) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
//查询指定用户权限列表
List<Permission> permissionList = permissionService.findByUid(uid);
for (Permission p:permissionList ) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(p.getPermissionName());
grantedAuthorities.add(grantedAuthority);
}
return grantedAuthorities;
}