它是Spring家族中的一个安全管理框架,具备功能有:身份认证、授权、防御常见攻击(CSRF、HTTP Headers、HTTP Requests),它的底层原理是传统的Servlet过滤器
。
官方文档:
https://docs.spring.io/spring-security/reference/index.html
spring-security官方案例:
https://github.com/spring-projects/spring-security-samples/tree/main)
UserDetailsService用来管理用户信息
@Configuration @EnableWebSecurity //Spring项目总需要添加此注解 public class WebSecurityConfig { @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser( User .withDefaultPasswordEncoder() .username("yan") //自定义用户名 .password("123456") //自定义密码 .roles("USER") //自定义角色 .build() ); return manager; } }
再次登录页面,必须输入所设置的用户名和密码,才可以登录成功,否则失败,不再使用Security默认的用户名和密码。
基础sql脚本
-- 创建数据库 CREATE DATABASE `security-demo`; USE `security-demo`; -- 创建用户表 CREATE TABLE `user`( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键', `username` VARCHAR(50) DEFAULT NULL COMMENT '用户名', `password` VARCHAR(500) DEFAULT NULL COMMENT '密码', `enabled` BOOLEAN NOT NULL COMMENT '是否启动' ); -- 唯一索引 CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`); -- 插入用户(密码是 "abc" ) INSERT INTO `user` (`username`, `password`, `enabled`) VALUES ('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE), ('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE), ('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
完成基础的增删改查…
@Configuration public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService { @Resource private UserMapper userMapper; // 数据库 - 用户表mapper @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // sql - 查询用户表 (根据用户名,进行查询用户表) User user = userMapper.selectOne(username); if (user == null) { throw new UsernameNotFoundException(username); } else { Collection authorities = new ArrayList<>(); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), user.getEnabled(), true, // 用户账号是否过期 true, // 用户凭证是否过期 true, // 用户是否未被锁定 authorities);// 权限列表 } } @Override public UserDetails updatePassword(UserDetails user, String newPassword) { return null; } // 创建用户 @Override public void createUser(UserDetails userDetails) { User user = new User(); user.setUsername(userDetails.getUsername()); user.setPassword(userDetails.getPassword()); user.setEnabled(true); userMapper.insert(user); } @Override public void updateUser(UserDetails user) { } @Override public void deleteUser(String username) { } @Override public void changePassword(String oldPassword, String newPassword) { } @Override public boolean userExists(String username) { return false; } }
Hash算法:
Spring Security的PasswordEncoder
接口用于对密码进行单向转换,使用哈希算法,例如MD5、SHA-256、SHA-512等,哈希算法是单向的,只能加密,不能解密。缺点:通过破解的方式猜测密码。
彩虹表:
恶意用户创建称为彩虹表的查找表,越是复杂的密码,需要的彩虹表就越大。
加盐密码:
盐是随机生产的字节(是明文的),盐和用户的密码一起经过哈希函数运算,生产一个哈希值,再与库的密码进行比较,可避免彩虹表,每个盐和密码它的哈希值都是不同的。
自适应单向函数:
随着硬件的不断发展,加盐哈希也不再安全。因为计算机可以每秒执行亿级别哈希运行,所有自适应单向函数,产生一个工作因子,来故意占用CPU、内存,让攻击者很难破解。
PasswordEncoder
创建对应的html的登录页,在Security自定义配置类里,添加:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests(a -> { a.anyRequest() // 对所有请求开启授权保护 .authenticated(); // 已认证的请求会自动授权 }) .formLogin(f -> { f.loginPage("/login").permitAll() // 登录页面无需授权即可访问 .usernameParameter("username") // 自定义表单用户名参数,默认是username .passwordParameter("password") // 自定义表单密码参数,默认是password .failureUrl("/login?error"); //登录失败的返回地址 }); // 表单授权方式 http.csrf(csrf -> csrf.disable()); // 暂时关闭csrf攻击 return http.build(); }
需要重写这2个过滤器,然后在Security自定义配置类进行对应的配置。
需要重写这个过滤器,然后在Security自定义配置类进行对应的配置。
当访问没有授权认证的接口,默认的Spring Security会使用AuthenticationEntryPoint
将用户请求跳转到登录页面,要求用户提供登录凭证,如果自定义的,可以设置返回的信息进行处理。
需要重写这个过滤器,然后在Security自定义配置类进行对应的配置。
# 在Security自定义配置类 这里添加 http.cors(withDefaults());
登录页面成功,在其他方法中可以得到当前登录用户的信息
// 存储认证对象的上下文 SecurityContext context = SecurityContextHolder.getContext(); // 认证对象 Authentication authentication = context.getAuthentication(); // 用户名 String username = authentication.getName(); // 身份 Object principal = authentication.getPrincipal(); // 凭证(脱敏) Object credentials = authentication.getCredentials(); //权限 Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
当我们同一个用户登录人数过多,会存在账号风险,可通过实现接口SessionInformationExpiredStrategy处理,然后在Security自定义配置类进行对应的配置。
//会话管理 http.sessionManagement(session -> { session .maximumSessions(1) // 代表当前登录同一个用户,数量超过1,触发该逻辑 .expiredSessionStrategy(new MySessionInformationExpiredStrategy()); // 重写的SessionInformationExpiredStrategy接口,处理返回逻辑 });
// 1.配置权限 [自定义配置] http.authorizeRequests( authorize -> authorize //具有LIST权限的用户可以访问/user/list .requestMatchers("/user/list").hasAuthority("LIST") //具有ADD权限的用户可以访问/user/add .requestMatchers("/user/save").hasAuthority("ADD") //对所有请求开启授权保护 .anyRequest() //已认证的请求会被自动授权 .authenticated() ); // 2 在登录页的接口,为当前登录的用户,赋值给对应的权限列表。 Collection authorities = new ArrayList<>(); authorities.add(() -> "LIST"); authorities.add(() -> "ADD");
需要重写这个过滤器,然后在Security自定义配置类进行对应的配置。
// 1.配置权限 [自定义配置] http.authorizeRequests( authorize -> authorize // 具有管理员角色(ADMIN)的用户可以访问/user/** .requestMatchers("/user/**").hasRole("ADMIN") //对所有请求开启授权保护 .anyRequest() //已认证的请求会被自动授权 .authenticated() ); // 2 在登录页的接口,为当前登录的用户,赋值给对应的权限列表。 UserDetails user = new org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .disabled(!user.getEnabled()) // 是否被禁用 .credentialsExpired(false) // 是否过期, false:未过期 .accountLocked(false) // 是否锁定, false:没有锁定 .roles("ADMIN") // 角色 - 有权限 - 正常访问接口 // .roles("ADMIN222") // 角色 - 没有授权的角色就是403 .build();
这样我们需要四张表(用户表、角色表、权限表、用户角色关联表),用户可以被分配一个或多个角色,而每个角色又可以具有一个或多个权限,通过对用户角色关联和角色权限关联表进行操作,可以管理访问权限。
# 用户表: user_id int 用户ID username varchar 用户名 password varchar 密码 # 角色表: role_id int 角色ID name varchar 角色名称 des varchar 描述 # 权限表: permission_id int 权限ID name varchar 权限名称 des varchar 描述 # 角色权限关联表 role_permission_id int 角色权限关联ID role_id int 角色ID permission_id int 权限ID
在配置文件中添加如下注解
@EnableMethodSecurity
在用户登录授权,进行授权角色或者权限资源
// 一般我们的角色、权限资源是通过SQL数据库中获取的 UserDetails user = org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .disabled(!user.getEnabled()) // 是否被禁用 .credentialsExpired(false) // 是否过期, false:未过期 .accountLocked(false) // 是否锁定, false:没有锁定 .roles("ADMIN") // 角色配置 .authorities("USER_ADD", "USER_UPDATE") // 权限资源,一但配置,上面的 roles 角色,会失效 .build();
常用授权注解
//用户必须有 ADMIN 角色 并且 用户名是 admin 才能访问此方法 @PreAuthorize("hasRole('ADMIN') and authentication.name == 'admim'") @GetMapping("/list") public String getList(){ return "list接口,返回信息”; } //用户必须有 USER_ADD 权限 才能访问此方法 @PreAuthorize("hasAuthority('USER_ADD')") @PostMapping("/add") public void add(@RequestBody User user){ return "add接口,返回信息”; }
OAuth2是一种开放授权协议。
OAuth 2协议包含以下角色:
下一篇:数据结构(3)(顺序栈)