Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。
应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标,认证和授权的概念是通用的而不只在Spring Security中。
“认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。
“授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。
1. 添加认证授权步骤
1.1 引入Security依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.2 编写Security配置类
查看spring官网,https://docs.spring.io/spring-security/site/docs/current/reference/html5/#samples,
点击进入GitHub示例工程中,在工程中可以看到一个配置类SecurityConfig,我们将该类复制过来。
package com.bjc.security.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(authorize -> authorize
.antMatchers("/css/**", "/index").permitAll()
.antMatchers("/user/**").hasRole("USER")
)
.formLogin(formLogin -> formLogin
.loginPage("/login")
.failureUrl("/login-error")
);
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
}
1.2.1 配置类
通过官网demo代码,可以知道该配置类需要继承WebSecurityConfigurerAdapter,并用注解@EnableWebSecurity标注,该注解被@Configuration标注了,所以我们不需要再该配置类上添加@Configuration注解。
1.2.2 安全登录控制请求访问权限
根据官网demo,可以知道我们可以重写configure方法,通过该方法的参数http来定制安全规则
package com.bjc.security.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求授权规则
http.authorizeRequests(authorize -> authorize
.antMatchers("/","/css/**", "/index").permitAll() // 访问首页、静态资源所有用户可以访问
.antMatchers("/level1/**").hasRole("VIP1") // 配置访问level1下的所有资源需要角色VIP1的用户
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3")
)
.formLogin(); // 开启自动配置的登录功能 如果访问没有权限,就会跳转/login来到登录页面(security自带的)
}
/**
* 配置认证用户
* */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内存用户(写死的用户)
auth.inMemoryAuthentication()
.passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence);
}
})
.withUser("zhangsan") // 用户名
.password("123456") // 密码
.roles("VIP1","VIP2") // 用户所属角色
.and() // 再添加一个
.withUser("lisi")
.password("123456")
.roles("VIP2,VIP3")
.and()
.withUser("wangwu")
.password("123456")
.roles("VIP2","VIP3");
// 通过数据库查询
// auth.jdbcAuthentication()
}
}
配置类写好之后,再次访问首页,点击资源如果没有登录会跳转到security内置的登录页面去进行登录。登录成功之后就可以访问对应的资源了,如果访问的资源没有权限,就会报错
注意:内存用户验证时,Spring boot 2.0.1引用的security 依赖是 spring security 5.X版本,此版本需要提供一个PasswordEncorder的实例,否则后台汇报错误:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"并且页面毫无响应。
1.2.3 添加注销功能
注销功能很简单,只需要开启注销功能,在配置类中添加http.logout();
然后在界面添加退出登录的form表单
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销">
</form>
这时候,当我们点击注销的时候,就自动退出了,security默认退出到/login资源,也就是退出到login页面,如果我们想退出之后回到首页该怎么做了?我们可以定制注销跳转资源
// 定制退出成功后,去到哪个地址
http.logout(cust -> {
cust.logoutSuccessUrl("/"); // 退出成功,去到首页
});
2. security与thymeleaf整合
我们可以利用thymeleaf的一些表达式和security整合的标签来控制权限资源的显示与隐藏
2.1 添加依赖
2.1.1 定义整合包版本
<properties>
<java.version>1.8</java.version>
<thymeleaf-extras-springsecurity.version>3.0.4.RELEASE</thymeleaf-extras-springsecurity.version>
</properties>
2.1.2 添加整合包
<!-- 引入 thymeleaf与security整合包-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>${thymeleaf-extras-springsecurity.version}</version>
</dependency>
注意:这里有兼容性问题,在boot2.1之前用的是 thymeleaf-extras-springsecurity4,因为这里版本是2.3,所以需要使用thymeleaf-extras-springsecurity5
2.2 引入security整合名称空间
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
注意:这里名称空间还是用thymeleaf-extras-springsecurity4即可
2.3 重要属性
2.3.1 sec:authorize——认证
该属性可以使用函数isAuthenticated()是否认证作为属性值,例如:
<div sec:authorize="!isAuthenticated()"> <!-- 如果没认证 -->
<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录</a></h2>
</div>
也可以使用函数 hasRole()判断当前用户是否有某种权限,例如:
<div sec:authorize="hasRole('VIP1')">
<h3 sec:authentication="">普通武功秘籍</h3>
<ul>
<li><a th:href="@{/level1/1}">罗汉拳</a></li>
<li><a th:href="@{/level1/2}">武当长拳</a></li>
<li><a th:href="@{/level1/3}">全真剑法</a></li>
</ul>
</div>
2.3.2 sec:authentication——认证信息
该属性可以获取一些认证信息,例如用户名,当前用户所拥有的角色等,例如:
<div sec:authorize="isAuthenticated()"> <!-- 如果认证了 -->
<h2> <span sec:authentication="name"><!-- 当前登录用户 -->
</span>,您好,您的角色有:<span sec:authentication="principal.authorities"></span><!-- 当前登录用户所属角色 -->
</h2>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销">
</form>
</div>
2.4 记住我功能
在配置类中开启记住我功能,下次登录可以免登录
http.rememberMe();
2.5 定制登录页
security默认给出的的登录页比较丑,我们可以定制我们自己的登录页
操作步骤:
1)准备自己的login.html
<div align="center">
<form th:action="@{/userlogin}" method="post">
用户名:<input name="user"/><br>
密码:<input name="pwd"><br/>
<input type="checkbox" name="rememberMe"/> 记住我<br/>
<input type="submit" value="登陆">
</form>
</div>
2)在配置类中配置定制的登录页
http.authorizeRequests(authorize -> authorize
.antMatchers("/","/css/**", "/index").permitAll() // 访问首页、静态资源所有用户可以访问
.antMatchers("/level1/**").hasRole("VIP1") // 配置访问level1下的所有资源需要角色VIP1的用户
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3")
)
//.formLogin() // 开启自动配置的登录功能 如果访问没有权限,就会跳转/login来到登录页面(security自带的)
.formLogin()
.usernameParameter("user")
.passwordParameter("pwd")
.loginPage("/userlogin");
注意:默认post形式的 /login代表处理登陆,一但定制loginPage;那么 loginPage的post请求就是登陆
3)配置记住我(可选配置)
http.rememberMe().rememberMeParameter("rememberMe");
注意:这里的rememberMeParameter的参数值与登录界面的保持一致。