搭建demo
- 如果不设置,默认账号user,密码会生成uuid打印在控制台中
- 可以在application.yml中设置
spring:
security:
user:
name: 123
password: 123
使用内存信息进行认证
- 继承WebSecurityConfigurerAdapter类,定义自己的配置文件
- 增加Configuration和EnableWebSecurity注解
- 重载configure(AuthenticationManagerBuilder auth)方法
- 密码必须加密,使用BCryptPasswordEncoder进行hash加密
- EnableGlobalMethodSecurity注解可以开启方法级别的权限控制
/**
* @EnableGlobalMethodSecurity: 启用方法级别的认证
* prePostEnabled: boolean 默认false
* true表示可以使用@PreAuthorize和@PostAuthorize
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = passwordEncoder();
// 定义两个角色,normal, admin
auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder.encode("123456"))
.roles("normal");
auth.inMemoryAuthentication().withUser("lisi").password(passwordEncoder.encode("123456"))
.roles("normal");
auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin"))
.roles("admin", "normal");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@RestController
@RequestMapping("/hello")
public class HelloWorldController {
@GetMapping("/world")
public String sayHello() {
return "Hello Srping security";
}
@GetMapping("/user")
@PreAuthorize(value = "hasAnyRole('admin', 'mormal')")
public String helloCommonUser() {
return "Hello all roles";
}
@GetMapping("/admin")
@PreAuthorize(value = "hasAnyRole('admin')")
public String helloAdminUser() {
return "Hello admin roles";
}
}
基于jdbc的用户认证
- 从数据库中获取用户信息(用户名,密码,角色)
- 在spring security中,用户信息的表示类是UserDetails(接口)
- User implements UserDetails,spring security使用User 对象进行认证
public interface UserDetails extends Serializable {
// 用户角色
Collection<? extends GrantedAuthority> getAuthorities(); // 角色集合
String getPassword();
String getUsername();
boolean isAccountNonExpired(); // 账号是否过期
boolean isAccountNonLocked(); // 账号是否锁定
boolean isCredentialsNonExpired(); // 证书是否过期
boolean isEnabled(); // 账号是否启用
}
- 实现UserDetailsService接口,根据username查询数据库得到User对象
@Component("MyUserDetailService")
public class MyUserDetailService implements UserDetailsService {
@Resource
private UserInfoMapper userInfoMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = null;
UserInfo userInfo = userInfoMapper.findByUserName(userName);
if (userInfo != null) {
ArrayList<GrantedAuthority> list = new ArrayList<>();
// 角色必须以ROLE_开头
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + userInfo.getRole());
list.add(authority);
user = new User(userInfo.getUsername(), userInfo.getPassword(), list);
}
return user;
}
}
- 修改配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("MyUserDetailService")
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// ------------ 基于数据库的验证-------------
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
// ------------基于内存的验证----------------------
// PasswordEncoder passwordEncoder = passwordEncoder();
// // 定义两个角色,normal, admin
// auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder.encode("123456"))
// .roles("normal");
//
// auth.inMemoryAuthentication().withUser("lisi").password(passwordEncoder.encode("123456"))
// .roles("normal");
//
// auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin"))
// .roles("admin", "normal");
}
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
}
认证和授权
- authentication:认证,认证访问者是谁。用户是不是当前系统的有效用户
- authorization:授权,访问者能做什么
RBAC模型
- role-based access control 基于角色的权限控制
- 权限:对资源的操作
- 角色:权限的集合,用户通过获得角色来获得权限
RBAC表设计
- 用户表
- 角色表
- 用户角色表(多对对关联表)
- 权限表
- 角色权限表(多对对关联表)
使用DetailsManager创建用户(manager提供了对用户的增删改查)
- JdbcUserDetailsManager和InMemoryUserDetailsManager基本相同
- 使用JdbcUserDetailsManager配置数据源,可以直接写进数据库,使用jdbcTemplate操作数据库
- 框架默认的角色和用户表 org\springframework\security\spring-security-core\5.4.5\spring-security-core-5.4.5.jar!\org\springframework\security\core\userdetails\jdbc\users.ddl
@Configuration
public class ApplicationConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private DataSource dataSource;
@Bean
public UserDetailsService getUserDetailsService() {
System.out.println("dataSource ---" + dataSource);
PasswordEncoder encoder = passwordEncoder();
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
// 如果数据库中已经存在该用户,则不创建
if (!manager.userExists("admin")) {
manager.createUser(
User.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN", "USER").build());
}
if (!manager.userExists("zs")) {
manager.createUser(
User.withUsername("zs")
.password(encoder.encode("123"))
.roles("USER").build());
}
return manager;
}
}
自定义角色用户表
CREATE TABLE sys_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR ( 100 ),
password VARCHAR ( 100 ),
realname VARCHAR ( 200 ),
is_enabled TINYINT,
is_locked TINYINT,
is_credential_expired TINYINT,
creation_time date,
login_time date
);
CREATE TABLE sys_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
rolename VARCHAR ( 255 ),
description VARCHAR ( 255 )
);
CREATE TABLE sys_user_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
role_id BIGINT
);
数据初始化
账号 |
密码 |
角色 |
zs |
123 |
USER |
lisi |
123 |
READ |
admin |
admin |
ADMIN、USER |
集成mybatis,SysUser implements UserDetails 自定义用户类实现userDetails接口,自定义登录界面,自定义登录失败界面,实现接口权限控制
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
SysUser sysUser = sysUserMapper.selectSysUser(userName);
if (sysUser != null) {
List<SysRole> roles = sysRoleMapper.selectRoleByUser(sysUser.getId());
List<GrantedAuthority> grantedAuthorities = roles.stream().map(SysRole::getRolename)
.map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(
Collectors.toList());
sysUser.setAuthorities(grantedAuthorities);
}
return sysUser;
}
// 重定向到index.html
@Controller
public class IndexController {
@GetMapping("/index")
public String toIndex() {
return "forward:/index.html";
}
}
<!--index.html-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>验证访问</p>
<a href="/hello/user">验证zs</a>
<a href="/hello/read">验证lisi</a>
<a href="/hello/admin">验证admin</a>
<a href="/logout">退出</a>
</body>
</html>
<!--mylogin.html-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义登录界面</title>
</head>
<body>
<p>自定义登录界面</p>
<!-- login接口是spring security自带 -->
<form action="/login" method="post">
<div>用户名:<input type="text" name="username"></div>
<div>密码:<input type="password" name="password"></div>
<input type="submit" value="登录">
</form>
</body>
</html>
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.authorizeRequests()
// inde是跳转的controller,mylogin自定义登录界面,/login是spring security UsernamePasswordAuthenticationFilter 默认的登录认证接口,可以随便写
.antMatchers("/index", "/mylogin.html", "/login", "/error.html").permitAll()
.antMatchers("/hello/user").hasAnyRole("USER")
.antMatchers("/hello/read").hasAnyRole("READ")
.antMatchers("/hello/admin").hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html") // 自定义登录界面
.loginProcessingUrl("/login") // spring security UsernamePasswordAuthenticationFilter 默认的登录认证接口,可以随便写
.failureUrl("/error.html") // 登陆错误页面
.and()
.csrf().disable(); // 禁用csrf token,开发中用oauth,CsrfFilter,通过对比cookie/session(CsrfToken类型)和header中的token(actualToken)进行校验
}
微服务权限案例
网友评论