0x01.Spring Security 概述
-
Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。
-
官网:(https://projects.spring.io/spring-security/)
-
Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。 特别要指出的是他们不能再WAR 或 EAR 级别进行移植。这样,如果你更换服务器环境,就要,在新的目标环境进行大量的工作,对你的应用系统进行重新配 置安全。使用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 certificate 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远程调用协议)。
0x02.依赖
<properties>
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</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-core</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring.security.version}</version>
</dependency>
0x03.具体使用
1.在web.xml中配置Spring Security的核心过滤器
<!--spring security配置文件-->
<display-name>SpringSecurity314</display-name>
<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>
</welcome-file-list>
- 同时需要制定核心配置文件
spring-security
的所在路径。
<!--指定spring配置文件的路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml,classpath*:spring-security.xml</param-value>
</context-param>
2.配置spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="/plugins/**" security="none"/>
<!--
配置具体的规则
auto-config="true" 不用自己编写登录的页面,框架提供默认登录页面
use-expressions="false" 是否使用SPEL表达式(没学习过)
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" -->
<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>
<!-- 定义跳转的具体的页面 -->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login.do"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
/>
<!-- 关闭跨域请求 -->
<security:csrf disabled="true"/>
<!-- 退出 -->
<security:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/login.jsp" />
</security:http>
<!-- 切换成数据库中的用户名和密码 -->
<security:authentication-manager>
<!--配置相关的service-->
<security:authentication-provider user-service-ref="userService">
<!-- 配置加密的方式 -->
<!-- <security:password-encoder ref="passwordEncoder"/>-->
</security:authentication-provider>
</security:authentication-manager>
<!-- 配置加密类 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
</beans>
3.编写serive相关接口和实现类
- service接口需要扩展
UserDetailsService
。
public interface UserService extends UserDetailsService {
}
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo=null;
try {
userInfo=userDao.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
User user= new User(userInfo.getUsername(),"{noop}"+userInfo.getPassword(),userInfo.getStatus()==0?false:true,true,true,true,getAuthority(userInfo.getRoles()));
return user;
}
private List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
List<SimpleGrantedAuthority> list=new ArrayList<>();
for(Role role:roles){
list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
}
return list;
}
}
4.实体类:
public class UserInfo {
private String id;
private String username;
private String email;
private String password;
private String phoneNum;
private int status;
private String statusStr;
private List<Role> roles;
// getters and setters
}
public class Role {
private String id;
private String roleName;
private String roleDesc;
private List<Permission> permissions;
private List<User> users;
//getters and setters
}
public class Permission {
private String id;
private String permissionName;
private String url;
private List<Role> roles;
//getters and setters
}
5.dao层(已配置MyBatis)
public interface UserDao {
@Select("select * from users where username=#{username}")
@Results({
@Result(id=true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "email",column = "email"),
@Result(property = "password",column = "password"),
@Result(property = "username",column = "username"),
@Result(property = "phoneNum",column = "phoneNum"),
@Result(property = "status",column = "status"),
@Result(property = "roles",column = "id",javaType = java.util.List.class,
many=@Many(select="com.atfwus.dao.RoleDao.findRoleByUserId"))
})
public UserInfo findByUsername(String Username) throws Exception;
}
public interface RoleDao {
@Select("select * from role where id in
(select roleId from users_role where userId=#{userId})")
public List<Role> findRoleByUserId(String userId) throws Exception;
}
细节部分:
- 简单使用可以暂时把加密配置去掉,不然与数据库的数据不匹配。
- 不适用加密的话,在密码传参的时候应该在密码前面加上
{noop}
,比如"{noop}"+userInfo.getPassword()
。 - 配置文件中已加入退出的相关配置,只需要访问
/logout.do
即可退出。
0x04.部分源码分析
1.springSecurityFilterChain
- 在
web.xml
文件中配置的过滤器是springSecurityFilterChain,真正执行的类是DelegatingFilterProxy。
- 查看DelegatingFilterProxy类,发现继承了一个父类GenericFilterBean。
- 查看GenericFilterBean类,发现它是一个
filter
,说明DelegatingFilterProxy也是一个filter
。
-
filter
真正起作用的是doFilter
方法,查看DelegatingFilterProxy的doFilter
方法。
-
我们能够得知,真正在发挥作用的还是这个
Filter
:delegate
。在下面可以发现有两个方法对delegate
进行了初始化:initFilterBean,initDelegate。 -
查看initFilterBean:
-
查看是如何获取名字的:
-
查看initDelegate:
-
到这我们可以发现,最终起作用的
delegate
其实就是我们在web,xml
中配置的名为springSecurityFilterChain的一个bean,事实上就是FilterChainProxy。 -
所以在在配置核心过滤器的时候,
filter
的名字一定得是springSecurityFilterChain。
2.spring-security配置文件
- 在源码的
spring.handers
中,可以发现具体由SecurityNamespaceHandler解析名称空间。
- 查看SecurityNamespaceHandler:
- 查看init方法:
- 继续跟进:
- 在这个类中,可以发现帮我们加载了具体的解析器。
- 查看HTTP的解析器:
- 查看
paras
方法:
- 进入方法中查看一下:
- 继续查看:
-
我们发现了springSecurityFilterChain,说明这个类真正的将
FilterChainProxy
注册了,并名字是springSecurityFilterChain,这也是上述配置的时候,名字为什么不能改的原因。 -
继续查看parse方法,发现创建了一个
FilterChain
:
- 点进去查看方法:
- 查看AuthenticationConfigBuilder部分:
- 我们可以发现在这个部分进行了很多的创建,这些都是我们在配置的时候指定的,这里是底层的具体实现,具体可以直接去相应的方法中查看。
Spring Security 源码版本为5.0.1.RELEASE
ATFWUS --Writing By 2020–05-09