一、SpringSecurity系列之web权限方案
1、自定义用户登录页面
(1)自定义用户登录处理类
不需要验证就可以直接访问controller返回的信息
代码实现如下:
package org.apache.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
//TODO 自定义用户登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后,跳转路径
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
}
(2)控制类
package org.apache.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String hello() {
return "hello security";
}
@GetMapping("index")
public String index() {
return "hello index";
}
}
(3)静态页面
在resource文件目录下创建static文件编写login.html文件
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>xx</title>
</head>
<body>
<form action="/login"method="post">
用户名:<input type="text"name="username"/><br/>
密码:<input type="password"name="password"/><br/>
<input type="submit"value="提交"/>
</form>
</body>
</html>
启动主程序类,访问:http://localhost:8111/test/hello
2、基于角色和权限访问控制
2.1、hasAuthority 方法
如果当前的主体具有指定的权限,有返回 true,没有则返回 false
- 在配置类设置当前访问地址有哪些权限
//表示当前登录用户具有admins权限时才可以访问这个路径
.antMatchers("/test/index").hasAuthority("admins")
当没有给权限时,我们再次登录
type=Forbidden:表示禁止访问,就是没有权限
- 在 UserDetailsService,把返回 User对象设置权限
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
再次访问成功看到 HelloIndex
2.2、hasAnyAuthority 方法
如果当前的主体具有在一些指定权限中是否有一个权限,有返回 true,没有则返回 false
.antMatchers("/test/index").hasAnyAuthority("admins","manage")
再次访问成功看到 Hello Index
hasAnyAuthority 方法
如果当前的主体具有在一些指定权限中是否有一个权限,有返回 true,没有则返回 false
.antMatchers("/test/index").hasAnyAuthority("admins","manage")
Test 我们给的权限是 admins,则满足条件可以正常访问
2.3、hasRole 方法
- 如果用户具备给定的角色就允许访问,否则出现403
- 如果当前主体具有指定的角色,则返回true
设置访问角色
.antMatchers("/test/index").hasRole("sale")
添加访问角色
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
添加角色时用逗号隔开,注意角色需要添加 ROLE_ 前缀
原因:源码展示
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null"); //判断是否为null
//在设置访问角色时如果前缀是 ROLE_ 开始,则返回false,并提示,不要自己添加前缀,因为是它自己添加的(在下面retrun中可以看出结果)
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'";
});
return "hasRole('ROLE_" + role + "')";
}
所以即使我们设置的权限没有 ROLE_ 前缀,而在添加权限时我们要加上这个前缀
2.4、hasAnyRole 方法
表示用户具备任何一个条件都可以访问
.antMatchers("/test/index").hasAnyRole("sale","develop")
3、自定义 403 界面
自定义 403 没有权限访问的页面
- 在静态资源文件夹中创建一个准备好的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>没有访问权限</h1>
</body>
</html>
设置访问配置类
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 自定义登录页面设置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedPage("/unAuthority.html");
}
}
当我们再次访问没有权限时,会跳转到刚才设置的界面,表示成功
4、注解的使用
4.1、@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀 ROLE_
- 使用前提,在启动类上或者配置类上添加下面注解
@EnableGlobalMethodSecurity(securedEnabled = true)
@SpringBootApplication
public class SecurityDemo1Application {
public static void main(String[] args) {
SpringApplication.run(SecurityDemo1Application.class, args);
}
}
在 controller的方法上面使用注解,设置角色
@Secured({"ROLE_sale","ROLE_manage"})
@GetMapping("delete")
public String delete(){
return "Hello Delete";
}
4.2、@PreAuthorize
注解适合进入方法前的权限验证,可以将登录用户的 roles/permissions 参数传到方法中
@PreAuthorize("hasAnyRole('admins')")
@GetMapping("delete")
public String delete(){
return "Hello Delete";
}
4.3、@PreAuthorize
注解适合进入方法前的权限验证,可以将登录用户的 roles/permissions 参数传到方法中
@PreAuthorize("hasAnyRole('admins')")
@GetMapping("delete")
public String delete(){
return "Hello Delete";
}
4.4、@PostAuthorize
表示方法执行后再进行校验
- 使用前提,开启注解使用(添加在配置类或启动类上)
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
@PostAuthorize("hasAnyRole('admins')") //方法执行之后校验
@GetMapping("delete")
public String delete(){
System.out.println("Hello ROLE_admins");
return "Hello Delete";
}
此时如果用户不是这个角色,那么访问后会跳转到403界面,都是还是会执行方法中的内容
Hello ROLE_admins
4.5、@PostFilter
权限验证之后对数据进行过滤
@PostFilter("filterObject.username == 'onana'")//只拿到 username为 onana 的数据,过滤掉其他数据
@PreAuthorize("hasAnyAuthority('admins')")
@GetMapping("getAll")
@ResponseBody
public List<Users> getAllUser(){
List<Users> users = new ArrayList<>();
users.add(new Users(null,"onana","123"));
users.add(new Users(null,"lsisi","123"));
return users;
}
结果
[
{
"id": null,
"username": "onana",
"password": "123"
}
]
4.6、@PreFilter
权限验证之前对数据进行过滤
5、用户注销
(1)在配置类中设置退出的配置
http.logout()
.logoutUrl("/logout") //设置退出的请求地址
.logoutSuccessUrl("/test/hello").permitAll(); //退出完成后跳转页面
(2)添加一个退出的请求路径
<a href="/logout">退出</a>
登录成功后跳转到 成功界面,然后可以正常访问有权限的地址,点击退出后再进行访问则需要登陆
6、自动登录(记住我)
实现原理:
(1)首先用户登入成功后,会生成一个 token(加密串),这个 token,一边相应给浏览器,放到 cookie中,一边使用 token 和用户信息 存储到数据库中,
(2)而以后再次访问时,浏览器获取 cookie信息,拿着cookie信息到数据库进行比对,如果查询到对应信息,则认证成功,实现自动登录
6.1、建表
DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
`username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`series` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`token` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
6.2、配置类
注入数据源,配置操作数据库对象
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
//注入数据源
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动创建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
在配置类的 configure方法中配置自动登录
http.formLogin()
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60) //设置有效时长(s)
.userDetailsService(userDetailsService)
在登录页面中添加一个复选框
<div>
<lable>记住我:</lable>
<input type="checkbox" name="remember-me"> <!--这里的name="remember-me" 是必须的 -->
</div>
当我们登入成功后,可以看到 security为我们自动封装的数据已经储存到数据库中了
7、CSRF
csrf指的是:跨站请求伪造(Cross-site request forgery),也被称为one-click attack 或者 session riding 通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已经登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是不是用户自愿发出的
<!-- 使用模板引擎 -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
实现原理:
在session中存放一个 token,保护 post,put,delete 请求,判断提交的请求中 token与session 中的是否一致,相同则放行