系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
认证︰用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。
常见的用户身份认证方式有︰用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
session认证:
用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了
token认证:
用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份
两者比较:
为什么要授权?
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源
授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问
主体—权限—资源
主体(用户id、账号、密码、…)
权限(权限id、权限标识、权限名称、资源名称、资源访问地址、…)
角色(角色id、角色名称、…)
角色和权限关系(角色id、权限id、…)
主体(用户)和角色关系(用户id、角色id、…)
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
1.SpringSecurity是什么
SpringSecurti基于Spring框架,提供了一套Web应用安全性的完整解决方案,一般来说,Web应用的安全性包括用户认证(Authenticataion)和用户授权(Authorization)两个部分,这两点也是Spring Security重要核心功能
(1)用户认证:用户是否能登录
(2)用户授权:用户是否有权限去做某些事情
2.SpringSecurity与Shiro对比
SpringSecurity特点:
(1)和Spring无缝整合
(2)全面的权限控制
(3)专门为Web开发而涉及
(4)重量级(缺点)
Shiro特点:
(1)轻量级
(2)通用性
总结: spring security功能强于Shiro,但比Shiro复杂
第一步:创建spring boot工程
第二步:引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
第三步:编写controller进行测试
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String test() {
return "hello,spring security";
}
}
SpringSecurity登录默认用户名:user
登录密码:启动控制台中找到
SpringSecurity本质是一个过滤器链,有很多过滤器
通过查看源码,可以发现:
1.UserDetailsService接口:
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
实现自己去查数据库的自定义认证步骤
2.PasswordEncoder接口: 数据机密接口,用于返回User对象里面的密码
方式一:通过配置文件
spring.security.user.name=admin
spring.security.user.password=admin
方式二:通过配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//密码进行加密操作
String password = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("xin").password(password).roles("admin");
}
//创建BCryptPasswordEncoder对象,否则报错:没有匹配关系
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}
方式三:自定义编写实现类
编写配置类
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("zx", new BCryptPasswordEncoder().encode("123"), auths);
}
}
编写实现类,返回User对象,User对象有用户名密码和权限
@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//创建BCryptPasswordEncoder对象,否则报错:没有匹配关系
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}
整合mybatisplus完成数据库操作
创建表和数据
实体类
整合mybatisplus制作mapper
配置文件添加数据库配置
完善登录实现类
@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> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
}
}
启动类添加扫描器
添加配置类
修改配置类
@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
...
@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防护
}
}
创建相关页面,controller
自定义登录页面:login.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username">
<br/>
密码:<input type="password" name="password">
<br/>
<input type="submit" value="login">
form>
body>
html>
注意:页面提交方式必须为 post 请求,所以上面的页面不能使用,用户名,密码必须为username,password
原因:在执行登录的时候会走一个过滤器UsernamePasswordAuthenticationFilter
表单的action配置的uri不需要配置controller(SpringSecurity默认帮我们做好了),同时这个uri必须和loginProcessingUrl方法里的值保持一致
第一个方法:hasAuthority方法(某个权限设置)
如果当前的主题具有指定的权限,则返回true,否则返回false
在配置类设置当前访问地址有哪些权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义自己编写的登录页面
...
//当前登录用户,只有具有admins权限才可以访问这个路径
.antMatchers("/test/index").hasAuthority("admins")
...
}
在userDetailService,把返回user对象设置权限
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
第二个方法:hasAnyAuthority 方法 (设置多个权限)
.antMatchers("/test/index").hasAnyAuthority("admins,manager")
第三个方法:hasRole 方法 (单个角色)
如果当前主体具有指定的角色,则返回true
.antMatchers("/test/index").hasRole("sale")
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
//springsecurity 会自动添加ROLE_前缀
第四个方法:hasAnyRole 方法(多个角色)
修改访问配置类
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
...
}
添加对应控制器
使用注解先要开启注解功能:在启动类或配置类上添加注解
@EnableGlobalMethodSecurity(securedEnabled = true)
@Secured:用户具有某个角色,可以访问方法,相当于配置类中的hasRole()方法,参数要以 ROLE_开头
在控制器方法上添加注解:
@GetMapping("update")
@Secured({"ROLE_sale","ROLE_manager"})
public String update() {
return "hello,spring update";
}
userDetailsService设置用户角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
@PreAuthorize:进入方法前的权限验证
@GetMapping("update")
//@Secured({"ROLE_sale","ROLE_manager"})
@PreAuthorize("hasAnyAuthority('admins')")
public String update() {
return "hello,spring update";
}
@PostAuthorize:执行方法后再进行权限验证,适合验证带有返回值的权限
@PostAuthorize("hasAnyAuthority('admins')")
@PreFilter:传入方法数据进行过滤
@PostFileter:方法返回数据进行过滤
在配置类中添加退出的配置
//退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
流程:
具体实现:
创建数据库表 persistent_logins(非必要,可以在配置类中设置自动创建)
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.setDataSource(dataSource);
// jdbcTokenRepository.setCreateTableOnStartup(true);//自动创建表
return jdbcTokenRepository;
}
配置类配置自动登录
.and().rememberMe().tokenRepository(persistentTokenRepository())//设置数据库操作对象
.tokenValiditySeconds(60)//设置有效时长,单位为秒
.userDetailsService(userDetailsService)
在登录页面中添加复选框
<input type="checkbox" name="remember-me">自动登录
注意:name属性值一定要是remember-me