Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI 和AOP(面向切面编程)功能为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
认证
是为用户建立一个他所声明的主体。主体一般是指用户,设备或可以在你系统中执行动作的其他系统,即登录过程
授权
指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由身份验证过程建立了,即当前登录用户可以具有哪些权限。
依赖引入(Spring相关+SpringSecurity相关+lombok)
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.0.2.RELEASE</spring.version>
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- SpringSecurity -->
<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-taglibs</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
SpringSecurity核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
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,表示拦截请求的配置。 -->
<!-- 不拦截静态资源,比如登录界面、注册界面、css、js等资源 -->
<http pattern="/img/**" security="none"/>
<http pattern="/login.html" security="none"/>
<!--
use-expressions:设置是否启用SpEL表达式,默认值是true
auto-config:是否使用自带的登录界面,不写默认为false
-->
<http use-expressions="false">
<!--
配置SpringSecurity的拦截路径(拦截规则)
- pattern:设置拦截规则
/* 表示根路径下所有资源(不包含子路径)
/** 表示根路径下所有资源(包含子路径)
- access:设置角色 - 角色命名为ROLE_角色名称,如ROLE_USER
-->
<intercept-url pattern = "/" access = "ROLE_USER"/>
<!--
开启表单验证
username-parameter = "username" 匹配前端页面表单,表单name为username
password-parameter = "password" 匹配前端页面表单,表单name为password
login-page:指定的登录页面
login-processing-url:登录请求的路径
default-target-url:登录成功(身份验证和授权)后跳转的地址
always-use-default-target:是否登录成功之后总是跳转到default-target-url指定的地址
authentication-failure-url:登录失败之后跳转的地址,小技巧:在地址栏中加一个参数,通过参数的值来判断是否登录成功,用于前端页面进行回显信息
authentication-success-handler-ref:default-target-url和always-use-default-target的替代属性,通过该属性直接跳到一个调度的Action,由这个Action去进行跳转(适用场景:当业务需要根据权限跳转到不同页面时)
-->
<form-login
login-page = "/login.html"
login-processing-url = "/login"
default-target-url = "/index.hmtl"
always-use-default-target = "true"
authentication-failure-url = "/login.html?loginError=true"
authentication-success-handler-ref = "loginSuccessHandler"
/>
<!-- 不使用csrf校验 -->
<csrf disabled="true"/>
<!-- 框架页面不拦截,即html页面可以使用iframe和frame标签 -->
<headers>
<frame-options policy="SAMEORIGIN" />
</headers>
<!--
注销配置
logout-url:注销请求的路径
logout-success-url:注销成功跳转的地址
invalidate-session:是否销毁Session
-->
<logout logout-url="/logout" logout-success-url="/login.html" invalidate-session="true" />
<!-- 记住我,免密登录,前端表单name为remember-me -->
<remember-me />
</http>
<!-- 配置认证管理器 -->
<authentication-manager>
<!--
认证的提供者
user-service-ref:自定义用户认证信息
-->
<authentication-provider user-service-ref="userDetailService">
<!-- 在认证权限管理器当中指定加密工具类 -->
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<!-- 自定义用户认证信息,自定义认证类需要实现UserDetailsService接口 -->
<beans:bean id="userDetailService" class="pers.liuchengyin.service.UserDetailServiceImpl"></beans:bean>
<!-- 加密工具类,对MD5进行随机加盐 -->
<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" ></beans:bean>
<!-- 登录成功,处理登录成功的Action,实现类需要实现AuthenticationSuccessHandler接口 -->
<beans:bean id="loginSuccessHandler" class="pers.liuchengyin.service.AuthenticationSuccessHandlerImpl" ></beans:bean>
</beans:beans>
配置文件中是通过authentication-success-handler-ref来进行处理登录成功的,因此这里给一个loginSuccessHandler的示例。该类实现了AuthenticationSuccessHandler接口。这个类的主要作用就是用来登录成功之后处理一些跳转之前的事情,或是根据角色权限来进行不同的跳转。
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
// 该方法会在登录成功时调用 - 在这个方法里可以通过判断角色的不同,拥有的权限不同,进行不同的跳转
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
System.out.println("登录成功");
// 这里要进行手动跳转
httpServletResponse.sendRedirect("/index.html");
}
}
配置文件中是自定义认证类来实现登录的,即可以从数据库中获取数据。这里给一个UserDetailServiceImpl的示例,该类实现了UserDetailsService接口。这个类里有一个loadUserByUsername的方法,它会在登录时被调用。
public class UserDetailServiceImpl implements UserDetailsService {
/**
* 该方法会在登录时调用 - 这里通常就是用来从数据库中获取角色和权限的
* @param username 用户名
* @return User对象
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 判断用户名是否为空
if(username == null || "".equals(username)){
return null;
}
// 定义权限的集合
List<GrantedAuthority> authorityList = new ArrayList<>();
// 从数据库中读取角色 - 这里直接写出来的
authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
// 从数据库中读取用户对象 - 这里直接写出来
Role role = new Role(username,"1234");
// 判断role是否为空
if(role != null){
// 密码前拼接上{noop} 表示不加密:{noop}1234
User user = new User(username,role.getPassword(),authorityList);
return user;
}
return null;
}
}
既然登录有了,那么注册是怎么做的呢?请看下面这个类
public class RoleController {
@Autowired
private RoleDao roleDao;
@Autowired
private PasswordEncoder passwordEncoder;
public void add(Role role){
// 获取明文密码
String password = role.getPassword();
// 对明文密码进行加密
String securityPassword = passwordEncoder.encode(password);
// 把加密后的密码存储到role对象中
role.setPassword(securityPassword);
// 调用数据库添加到数据库去
roleDao.add(role);
}
那么它内部是怎么加密的呢?其实就是通过BCryptpassword这个类生成随机的盐然后对原密码混淆。
具体,我这里就不谈了,看下面这篇文章,我也是看了这篇文章才明白的。