Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松地扩展以满足定制需求
环境准备
-
静态资源
素材下载 -
编写路由controller
@Controller
public class RouterController {
@GetMapping({"/", "/index", "/index.html"})
public String index() {
return "index";
}
@GetMapping({"/toLogin", "/login.html"})
public String login() {
return "views/login";
}
@GetMapping("/level1/{index}")
public String level1(@PathVariable("index") int index) {
return "views/level1/"+index;
}
@GetMapping("/level2/{index}")
public String level2(@PathVariable("index") int index) {
return "views/level2/"+index;
}
@GetMapping("/level3/{index}")
public String level3(@PathVariable("index") int index) {
return "views/level3/"+index;
}
}
测试环境
Spring Security
要使用spring security只要引入依赖spring-boot-starter-security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
重要的几个类:
-
WebSecurityConfigurerAdapter:自定义Security策略
-
AuthenticationManagerBuilder:自定义认证策略
-
@EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在
认证和授权
- 编写spring security配置类
package com.wcy.config;
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;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
- 自定义授权
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
// 所有人可以访问首页,但是level页面只有对应权限的人可以访问
http.authorizeRequests()
.antMatchers("/", "/index", "/index.html").permitAll()
.antMatchers("/level1/**").hasAnyRole("v1")
.antMatchers("/level2/**").hasAnyRole("v2")
.antMatchers("/level3/**").hasAnyRole("v3");
// 权限不足跳转到登陆页面
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin();
}
- 此时测试会发现除了主页面,点击其他页面都会进入/login页面
因为权限不足
- 自定义认证
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从内存定义用户,也可以在jdbc中拿
// 用and()可以连接多个用户
// 密码必须要加密
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("wcy").password(new BCryptPasswordEncoder().encode("123")).roles("v1", "v2", "v3")
.and()
.withUser("lqy").password(new BCryptPasswordEncoder().encode("123")).roles("v1", "v2")
.and()
.withUser("sep").password(new BCryptPasswordEncoder().encode("123")).roles("v1");
}
此时再去访问,登录后就可以访问对应权限的页面
注销
注销需要在前端添加一个链接/logout
<a class="item" th:href="@{/logout}">
<i class="address card icon"></i> 注销
</a>
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
// 所有人可以访问首页,但是level页面只有对应权限的人可以访问
http.authorizeRequests()
.antMatchers("/", "/index", "/index.html").permitAll()
.antMatchers("/level1/**").hasAnyRole("v1")
.antMatchers("/level2/**").hasAnyRole("v2")
.antMatchers("/level3/**").hasAnyRole("v3");
// 权限不足跳转到登陆页面
http.formLogin();
// 注销及注销后返回的页面
http.logout().logoutSuccessUrl("/");
}
控制权限
用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮
还有就是,比如一个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能的菜单,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?
- 首先需要结合thymeleaf来实现,导入依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
- 前端页面导入命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
- 使用
sec:authorize="isAuthenticated()"
:判断是否认证登录!用来显示不同的页面
<!--未登录-->
<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="address card icon"></i> 注销
</a>
</div>
测试,现在实现了该功能
如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();
http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");
下面继续实现菜单权限控制的功能
使用sec:authorize="hasRole('v1')"
来判断是否有v1权限
<div class="column" sec:authorize="hasRole('v1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('v2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('v3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
此时如果再访问,只能访问对应权限的菜单
记住我
现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单
1、开启记住我功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//记住我
http.rememberMe();
}
2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在
实际就是登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie
定制登陆页面
之前权限不足的登陆页面都是框架提供的,那想要使用自己的登陆页面该如何做?
只需要指定到我们自己登陆页面的路由即可
// 权限不足跳转到登陆页面 指定登陆页面
http.formLogin().loginPage("/toLogin");
不过现在登陆会发生404的错误,因为我们使用的是自己的登陆页面,但是我们并没有自己写登陆的逻辑代码,所以还是需要交给框架来做
只需要指定处理的url
// 权限不足跳转到登陆页面 指定登陆页面
http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
前端需要路由跳转
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
登陆表单提交到/login
<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>
这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码,我们配置接收登录的用户名和密码的参数
默认情况下,参数的名称就是username和password,如果表单中的名称不是这样,就使用usernameParameter和passwordParameter来指定
http.formLogin()
.loginPage("/toLogin")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password");
现在就可以使用自己的登陆页面来实现
在登陆页面中添加记住我功能
首先在form表单中添加
<input type="radio" name="remember">记住我
然后后台处理参数即可
// 记住我
http.rememberMe().rememberMeParameter("remember");