3.SrpingSecuriyt web 权限方案
认证
3.1设置登录的用户名和密码
3.1.1通过配置文件
3.1.2通过配置类
创建SecurityConfig类继承WebSecurityConfigurerAdapter重写configure方法
package com.example.demo.config;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String passWord = bCryptPasswordEncoder.encode("wx");
auth.inMemoryAuthentication().withUser("root").password(passWord).roles("admin");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
3.1.3自定义编写实现类
-
创建配置类,设置使用那个UserDetailsServices实现类
package com.example.demo.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.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(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
-
编写UserDetailsService实现类,返回User对象,User对象有用户名密码和操作权限
package com.example.demo.service;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("root",new BCryptPasswordEncoder().encode("wx"),role);
}
}
3.2实现数据库认证来完成用户登录
3.2.1 导入相关的依赖
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok 用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
3.2.2创建数据库和数据库表
CREATE TABLE users ( id BIGINT PRIMARY KEY auto_increment, username VARCHAR ( 20 ) UNIQUE NOT NULL, PASSWORD VARCHAR ( 100 ) );-- 密码 atguigu
INSERT INTO users
VALUES
( 1, '张san', '$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na' );-- 密码 atguigu
INSERT INTO users
VALUES
( 2, '李si', '$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na' );
CREATE TABLE role ( id BIGINT PRIMARY KEY auto_increment, NAME VARCHAR ( 20 ) );
INSERT INTO role
VALUES
( 1, '管理员' );
INSERT INTO role
VALUES
( 2, '普通用户' );
CREATE TABLE role_user ( uid BIGINT, rid BIGINT );
INSERT INTO role_user
VALUES
( 1, 1 );
INSERT INTO role_user
VALUES
( 2, 2 );
CREATE TABLE menu ( id BIGINT PRIMARY KEY auto_increment, NAME VARCHAR ( 20 ), url VARCHAR ( 100 ), parentid BIGINT, permission VARCHAR ( 20 ) );
INSERT INTO menu
VALUES
( 1, '系统管理', '', 0, 'menu:system' );
INSERT INTO menu
VALUES
( 2, '用户管理', '', 0, 'menu:user' );
CREATE TABLE role_menu ( mid BIGINT, rid BIGINT );
INSERT INTO role_menu
VALUES
( 1, 1 );
INSERT INTO role_menu
VALUES
( 2, 1 );
INSERT INTO role_menu
VALUES
(
2,
2)
3.2.3创建实体类
package com.example.demo.entity;
import lombok.Data;
@Data
public class Users {
private Integer id;
private String username;
private String password;
}
3.2.4整合MyBatisPlus完成数据库操作
创建接口继承BaseMapper
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.Users;
public interface UsersMapper extends BaseMapper<Users> {
}
3.2.5在MyUserDetailsService调用mapper里面的方法查询数据库进行用户认证
package com.example.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.entity.Users;
import com.example.demo.mapper.UsersMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用usersMapper方法,更加用户名查询数据库
QueryWrapper<Users> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
Users users = usersMapper.selectOne(wrapper);
if (users == null){
throw new UsernameNotFoundException("用户名不再存");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
//从数据库中产线返回users对象,得到用户名和密码,返回
return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),role);
}
}
3.2.6在启动类上加注解
@MapperScan(“com.example.demo.mapper”)
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.2.7 配置数据库信息
#mysql 数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_security?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
3.3 自定义设置登录页面
3.3.1 在配置类实现相关的配置
在配置类中SecurityConfigTest,重写WebSecurityConfigurerAdapter父类方法configure(HttpSecurity http)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义自己编写的登录页面
.loginPage("/login.html")//登录页面设置
.loginProcessingUrl("/user/login")//登录访问路径
.defaultSuccessUrl("/test/index").permitAll()//登录成功之后,跳转路径
.and().authorizeRequests()
.antMatchers("/","test/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
.anyRequest().authenticated()
.and().csrf().disable();//关闭csrf防护
}
3.3.2 创建相关的页面,包含controller
创建login.html,注意表单中name,必须是username,password,因为springSecurity中UsernamePasswordAuthenticationFilter过滤器中接受表单的名字是,
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username">
<br/>
用户名:<input type="text" name="password">
<br/>
<input type="submit" value="login">
</form>
</body>
</html>
TestController中曾加方法用于登录成功后的跳转
@GetMapping("/index")
public String index(){
return "hello index";
}
如果是表单name名称不同可通过配置类中设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-do8RxdJ8-1667583902396)(C:\Users\LonelyBean\AppData\Roaming\Typora\typora-user-images\image-20221020160538103.png)]
授权
3.4 基于校色或权限进行访问控制
3.4.1 hasAuthority 方法
如果当前的主体具有指定的权限,则返回 true,否则返回 false
-
在配置类中设置当前访问地址有哪些权限
//当前登录用户,只有具有admins权限才可以访问这个路径 .antMatchers("/test/hello").hasAnyAuthority("admins")
-
在MyUserDetailsService,把返回User对象设置权限
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
3.4.2 hasAnyAuthority方法
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.
3.4.3 hasRole方法
如果用户具备给定角色就允许访问,否则出现 403。
如果当前主体具有指定的角色,则返回 true。
源码:
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
} else {
return "hasRole('ROLE_" + role + "')";
}
}
配置类中:
3.4.4 hasAnyRole方法
表示用户具备任何一个条件都可以访问。
配置类
给用户添加角色
3.4.5 自定义403无权限访问
-
创建unauth.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>没有访问权限</h1> </body> </html>
-
配置类
//配置没有权限访问跳转自定义页面 http.exceptionHandling().accessDeniedPage("/unauth.html");
3.5 认证授权过程中注解使用
3.5.1@Secured
用户具有目个角色,可以访问方法
-
启动类或者配置类上
@EnableGlobalMethodSecurity(securedEnabled=true)
-
增加controller方法
@GetMapping("update") @Secured({"ROLE_sale","ROLE_manager"}) public String update(){ return "hello update"; }
-
给用户添加角色
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_manager1,ROLE_sale");
3.5.2 @PreAuthorize
@PreAuthorize:注解适合进入方法前的权限验证,
@PreAuthorize 可以将登录用户的 roles/permissions 参数传到方法中。
-
启动类或配置类上开启注解
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
-
添加controller注解
@GetMapping("update") //@Secured({"ROLE_sale","ROLE_manager"}) @PreAuthorize("hasAnyAuthority('admins')") public String update(){ return "hello update"; }
3.5.3@PostAuthorize
-
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.
- 开启注解
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
-
添加controller注解
@GetMapping("/update")
//@Secured({"ROLE_sale","ROLE_manager"})
// @PreAuthorize("hasAnyAuthority('admins')")
@PostAuthorize("hasAnyAuthority('admins')")
public String update(){
System.out.println("update.............");
return "hello update";
}
方法执行之后验证
3.5.4@PostFilter
@PostFilter对方法返回的数据进行过滤
@GetMapping("getAll")
@PostAuthorize("hasRole('ROLE_admins')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<Users> getAllUser(){
ArrayList<Users> list = new ArrayList<>();
list.add(new Users(1,"admin1","6666"));
list.add(new Users(2,"admin2","888"));
System.out.println(list);
return list;
}
3.5.5@PreFilter
@PreFilter对传入的数据进行过滤
@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo>
list){
list.forEach(t-> {
System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}
3.6用户退出操作
-
在配置类中添加退出的配置
//退出 http.logout().logoutUrl("/logout").logoutSuccessUrl("/login.html");
-
测试
- 修改配置类,登录成功后跳转到成功页面
- 在成功页面添加超链接,写设置退出的路径
3.7 记住我
- cookie技术
- 安全框架机制实现自动登录
3.7.1 实现原理
springSecuriyt实现原理
3.7.2 具体实现
-
创建数据库
CREATE TABLE persistent_logins ( username VARCHAR ( 64 ) NOT NULL, series VARCHAR ( 64 ) PRIMARY KEY, token VARCHAR ( 64 ) NOT NULL, last_used TIMESTAMP NOT NULL )
-
配置类中注入数据源
@Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); //自动生成数据库表 // jdbcTokenRepository.setCreateTableOnStartup(true); jdbcTokenRepository.setDataSource(dataSource); return new JdbcTokenRepositoryImpl(); }
-
配置类中设置记住我
//记住我设置 .and().rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60) .userDetailsService(userDetailsService)
-
在登录界面中添加复选框
记住我: <input type="checkbox" name="remember-me">自动登录
3.8 CSRF理解
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已 登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个 自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买 商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。 这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的 浏览器,却不能保证请求本身是用户自愿发出的。
从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用 程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。