一,Spring Security简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
它是一个认证【身份认证】及授权【权限控制】的框架。shiro是它的对标框架,springsecurity已经开发很多年了,由于shiro的优势,它发展的不是很好,直到springboot出现,security的配置变的非常简单,现在微服务的项目都会优先选择security框架。security实现原理是通过Filter实现的。
二,项目中应用Spring Security
1.启动认证
1.1创建maven项目
1.2.添加pom依赖
<dependencies>
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<!--Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
<scope>compile</scope>
</dependency>
</dependencies>
1.3.配置yml端口号
server:
port: 8181
1.4.编写控制器
@Controller
public class DemoController {
@RequestMapping("/main")
public String show(){
return "main";
}
}
1.5.编写templates主页面
1.6.编写启动类:
@SpringBootApplication
public class MainTest {
public static void main(String[] args) {
SpringApplication.run(MainTest.class,args);
}
}
1.7.启动项目,访问/main: 自动跳转到:
启动项目,访问项目主页面http://localhost/main,项目会自动跳转到一个登录页面。这代表Spring Security已经开启了认证功能,不登录无法访问所有资源,该页面就是Spring Security自带的登录页面。
完成一下Spring Security 自带的登录功能:
用户名默认:user
密码为 idea控制台输出的内容 :
登录成功后:自动跳转到 刚刚想要访问的页面!
2.自定义认证逻辑
当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现UserDetailsService接口即可。接口定义如下:
2.1.编写SecurityConfig 配置类:
package com.jr.config;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPwdEncoder(){
return new BCryptPasswordEncoder();//密码加密处理(输入不可见)
}
}
2.2.编写UserDetailsServiceImpl ,UserDetailsService接口的实现类,用于实现自定义逻辑:
package com.jr.service.serviceImpl;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1. 查询数据库判断用户名是否存在,如果不存在抛出UsernameNotFoundException
if(!username.equals("admin")){
throw new UsernameNotFoundException("用户名不存在");
}
//把查询出来的密码进行解析,或直接把password放到构造方法中。
//理解:password就是数据库中查询出来的密码,查询出来的内容不是123
String password = encoder.encode("123");
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
//匹配成功后将用户和密码存入User对象,User对象是系统自带的
}
}
2.3.重启项目
在页面中输入账号:admin,密码:123。后可以正确进入到main.html页面。
3.自定义登录页面
虽然Spring Security给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页面。所以Spring Security中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。
3.1.编写login.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>内容</title>
</head>
<body>
<form action="/login" method="post">
<input type="text" name="username"/>
<input type="password" name="password"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
3.2.修改配置类:
修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapter,并重写configure方法。
successForwardUrl()登录成功后跳转地址
loginPage() 登录页面
loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。
antMatchers():匹配内容
permitAll():允许
package com.jr.config;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单认证
http.formLogin()
.loginProcessingUrl("/login")
//此处表单不写action属性也可以 //当发现/login时认为是登录,需要执行UserDetailsServiceImpl
.successForwardUrl("/main") //此处是post请求
.loginPage("/login.html");
// url 拦截
http.authorizeRequests()
.antMatchers("/login.html").permitAll() //login.html不需要被认证
.anyRequest().authenticated();//所有的请求都必须被认证。必须登录后才能访问。
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPwdEncoder(){
return new BCryptPasswordEncoder();
}
}
4.记忆(Remember Me) 功能实现
4.1.添加依赖:
<!-- 4.添加mysql数据库的驱动包依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 3.添加Mybatis 的启动器依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
4.2.修改配置文件,添加连接数据库四要素
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/security
4.3.添加配置文件:
package com.jr.config;
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository getPersistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl=new JdbcTokenRepositoryImpl();
jdbcTokenRepositoryImpl.setDataSource(dataSource);
//自动建表,第一次启动时需要,第二次启动时注释掉
//jdbcTokenRepositoryImpl.setCreateTableOnStartup(true); //该代码不好用,使用sql手动创建库,表
return jdbcTokenRepositoryImpl;
}
}
4.4.修改 SecurityConfig 配置文件:
@Autowired
PersistentTokenRepository repository;
// private RememberMeConfig repository;
@Autowired
private UserDetailsServiceImpl userDetailsService;
http.rememberMe()
.userDetailsService(userDetailsService) //登录逻辑交给哪个对象
.tokenRepository(repository); //持久层对象
4.5.修改登录页面:
<input type="checkbox" name="remember-me" value="true"/> <br/>
4.6.创建数据库 security ,创建数据库表persistent_logins语句:
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
【说明:重启服务器之后,仍然可以记住你!访问/main,可以直接跳转到main页面,不会再要求登录了。】
5.Thymeleaf中Spring Security的使用
5.1.获得属性
可以在html页面中通过 sec:authentication=""获取UsernamePasswordAuthenticationToken中所有getXXX的内容,包含父类中的getXXX的内容。
根据源码得出下面属性:
name:登录账号名称
principal:登录主体,在自定义登录逻辑中是UserDetails
credentials:凭证(密码)
authorities:权限和角色
details:实际上是WebAuthenticationDetails的实例。可以获取remoteAddress(客户端ip)和sessionId(当前sessionId)
1.添加依赖:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
2.创建页面:
在项目resources中新建templates文件夹,在templates中新建demo.html页面
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name">123</span><br/>
登录账号:<span sec:authentication="principal.username">456</span><br/>
凭证:<span sec:authentication="credentials">456</span><br/>
权限和角色:<span sec:authentication="authorities">456</span><br/>
客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/>
sessionId:<span sec:authentication="details.sessionId">456</span><br/>
</body>
</html>
3.编写控制器:
@RequestMapping("/demo")
public String demo(){
return "demo";
}
5.2.权限判断
在html页面中可以使用sec:authorize=”表达式”进行权限控制,判断是否显示某些内容。表达式的内容和access(表达式)的用法相同。如果用户具有指定的权限,则显示对应的内容;如果表达式不成立,则不显示对应的元素。
1.设置用户角色和权限
设定用户具有admin,/insert,/delete权限ROLE_abc角色。
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc,/insert,/delete"));
2.修改demo页面,展示效果:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name">123</span><br/>
登录账号:<span sec:authentication="principal.username">456</span><br/>
凭证:<span sec:authentication="credentials">456</span><br/>
权限和角色:<span sec:authentication="authorities">456</span><br/>
客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/>
sessionId:<span sec:authentication="details.sessionId">456</span><br/>
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('abc')">新增</button>
<button sec:authorize="hasRole('abc')">删除</button>
<button sec:authorize="hasRole('abc')">修改</button>
<button sec:authorize="hasRole('abc1')">查看</button>
</body>
</html>
6.退出登录
用户只需要向Spring Security项目中发送/logout退出请求即可。
1.修改demo页面:
<a href="/logout">退出登录</a>
7.Spring Security中CSRF
从刚开始学习Spring Security时,在配置类中一直存在这样一行代码:http.csrf().disable();如果没有这行代码导致用户无法被认证。这行代码的含义是:关闭csrf防护。
7.1什么是CSRF
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求 访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。
7.2Spring Security中CSRF
从Spring Security4开始CSRF防护默认开启。默认会拦截请求。进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。