1、安全简介
-
在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来
如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:
- 一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取
- 另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程
因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中
市面上存在比较有名的:Shiro,Spring Security !
首先我们看下它的官网介绍:Spring Security官网介绍
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
从官网的介绍中可以知道这是一个权限框架
对于权限 一般会细分为功能权限,访问权限,和菜单权限
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
一般来说,Web 应用的安全性包括 用户认证(Authentication) 和 用户授权(Authorization) 两个部分。
-
用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统
用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程
-
用户授权指的是验证某个用户是否有权限执行某个操作
在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。
一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。
-
在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等
-
在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制
2、实验环境搭建
1)静态资源
2)跳转Controller
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
3、认识SpringSecurity
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,可以实现强大的Web安全控制,对于安全控制,仅需要引入 spring-boot-starter-security
模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
WebSecurityConfigurerAdapter
:自定义Security策略AuthenticationManagerBuilder
:自定义认证策略@EnableWebSecurity
:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
4、认证和授权
1)引入 Security 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2)Security 配置类
@EnableWebSecurity
开启WebSecurity模式
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
3)配置授权规则
/**
* 配置授权规则
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 对http请求进行授权
http.authorizeHttpRequests()
// 首页允许所有人访问
.mvcMatchers("/").permitAll()
// "/level1/**" 只允许 vip1角色 访问
.mvcMatchers("/level1/**").hasRole("vip1")
.mvcMatchers("/level2/**").hasRole("vip2")
.mvcMatchers("/level3/**").hasRole("vip3");
}
测试:除了首页都进不去了!因为目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!
4)开启自动配置的登录功能
在configure()方法中加入以下配置
http.formLogin();
/login
请求来到登录页
/login?error
登录失败
测试:访问没有权限的页面时会跳转至系统的登录页
5)配置认证规则
重写
configure(AuthenticationManagerBuilder auth)
方法
/**
* 配置认证规则
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 在内存中定义,也可以在jdbc中去拿
auth.inMemoryAuthentication()
.withUser("tuwer").password("123456").roles("vip2","vip3")
.and()
.withUser("root").password("123456").roles("vip1","vip2","vip3")
.and()
.withUser("guest").password("123456").roles("vip1");
}
测试:出错!密码未加密!
要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码
/**
* 配置认证规则
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 在内存中定义,也可以在jdbc中去拿
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("tuwer").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
测试:登录成功,并且每个角色只能访问自己认证下的规则!
5、权限控制和注销
1)开启自动配置的注销的功能
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
// 开启自动配置的注销的功能
// /logout 注销请求
http.logout();
}
2)前端增加注销按钮
<a class="item" th:href="@{/logout}">
<i class="address card icon"></i> 注销
</a>
测试:注销完毕会跳转到登录页面!
3)注销后转至指定页面
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");
4)前端权限控制
前端只显示与当前用户匹配的内容;当前用户没有权限访问的内容不显示
需要结合 thymeleaf 中的一些功能
sec:authorize="isAuthenticated()"
表示已认证登录!
(1)导入依赖
注意版本问题;有些版本在thymeleaf中不支持
thymeleaf-extras-springsecurity5
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
(2)修改前端
- 命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
- 权限控制
<!--如果未登录-->
<div sec:authorize = "!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果已登录-->
<div sec:authorize = "isAuthenticated()">
<a class="item">
<i class="address card icon"></i>
用户名:<span sec:authentication = "principal.username"></span>
角色:<span sec:authentication = "principal.authorities"></span>
</a>
</div>
<div sec:authorize = "isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
sec:authentication = "principal.username"
登录用户名字sec:authentication = "principal.authorities"
登录用户的权限
5)csrf跨站请求伪造
如果注销后,出现404,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题
- 可以将请求改为post表单提交
- 或者在spring security中关闭csrf功能;在配置中增加
http.csrf().disable();
// 关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();
// 注销
http.logout().logoutSuccessUrl("/");
6、记住我
1)开启记住我功能
/**
* 配置授权规则
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
// 记住我
http.rememberMe();
}
2)测试
登录页多了一个记住我功能,登录之后关闭浏览器,然后重新打开浏览器访问,发现用户依旧存在!
3)原理分析
登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie
cookie 默认保留14天(即两周)
4)alwaysRemember(true)
表示总是记住;为 true 时,不管用户是否勾选记住我,都会记住;即使没有勾选,也会创建cookie;默认有效期仍然是14天
7、定制登录页
1)指定登录页
http.formLogin().loginPage("/toLogin");
2)前端登录按钮
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
1) 和 2)中都指向自定义的登录页
3)自定义登录页
表单必须是 post 方式
<form th:action="@{/login}" method="post">
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="username">
<i class="user icon"></i>
</div>
</div>
<div class="field">
<label>Password</label>
<div class="ui left icon input">
<input type="password" name="password">
<i class="lock icon"></i>
</div>
</div>
<input type="submit" class="ui blue submit button"/>
</form>
4)用户名、密码
默认的用户名名称:
username
,密码名称:password
如果自定义的用户名名称和密码名称与默认的不一致,可以通过如下方式指定
如:用户名 name,密码:ps
http.formLogin()
.loginPage("/toLogin")
.usernameParameter("name")
.passwordParameter("ps");
5)登录处理地址
处理登录页表单数据的地址,也就是表单要提交的地址
/toLogin
登录页地址
/login
表单提交地址
http.formLogin()
.loginPage("/toLogin")
.usernameParameter("name")
.passwordParameter("ps")
.loginProcessingUrl("/login");
6)记住我
- 前端登录页
<input type="checkbox" name="remember"> 记住我
- 后端
http.rememberMe()
// 定制记住我的参数!
.rememberMeParameter("remember");
8、完整配置类
package com.tuwer.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
/**
* @author 土味儿
* Date 2022/1/22
* @version 1.0
* 自定义安全配置类
* 开启WebSecurity模式
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置授权规则
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 对http请求进行授权
http.authorizeHttpRequests()
// 首页允许所有人访问
.mvcMatchers("/").permitAll()
// "/level1/**" 只允许 vip1角色 访问
.mvcMatchers("/level1/**").hasRole("vip1")
.mvcMatchers("/level2/**").hasRole("vip2")
.mvcMatchers("/level3/**").hasRole("vip3");
// 开启自动配置的登录功能;当访问没有权限的资源时会跳转到登录页
http.formLogin()
.loginPage("/toLogin")
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("ps");
// 记住我
http.rememberMe()
.alwaysRemember(true)
.rememberMeParameter("remember");
// 关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();
// 注销
http.logout().logoutSuccessUrl("/");
}
/**
* 配置认证规则
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
//要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
//spring security 官方推荐的是使用bcrypt加密方式。
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("tuwer").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}