用户认证
我们在进行认证的时候需要输入账号和密码。如何自定义呢
- 配置文件
- 配置类
- 自定义配置类
配置文件
spring:
security:
user:
name: wsl
password: 123123
配置类
@Configuration
public class configsecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 这里表示设置用户名和密码
// auth.inMemoryAuthentication().withUser("wsl").password("123").roles("admin");
// 通过密码编码器
BCryptPasswordEncoder Password = new BCryptPasswordEncoder();
String encode = password().encode("123");
System.out.println("密码是:"+encode);
auth.inMemoryAuthentication().withUser("wsl").password(encode).roles("admin");
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
自定义配置类
我们在实际中应该是通过数据库中读取账号密码的
首先这里我们需要进行数据库操作。就需要数据库和搭建Mybtis-plus。
当然也可以使用其他数据库访问框架
环境搭建
数据库和数据库访问层
数据库
CREATE DATABASE securityTest
CREATE TABLE users
(id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) ,
`password` VARCHAR(255))
INSERT INTO users (username,`password`) VALUES
('wsl','123'),
('wsm','321')
搭建数据访问层
导入依赖
<dependencies>
<!--引入SpringBoot的 web starter依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入SpringBoot的 security starter依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--数据库需要的jar文件-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
</dependency>
</dependencies>
yaml
server:
port: 8888
spring:
datasource:
username: root
password: ok
url: jdbc:mysql://localhost:3306/securityTest?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
#Mybatis-Plus的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置在控制台打印 sql语句
# 配置自定义sql语句的 *mapper.xml 文件位置
mapper-locations: classpath:**/mapper/**.xml
实体类
使用lombok,生成get、set
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Component("users")
public class Users {
@TableId(type = IdType.AUTO)
private long id;
private String username;
private String password;
}
数据访问层
@Mapper
public interface UsersDao extends BaseMapper<Users> {
}
自定义配置类访问数据库
下面开始进行自定义密码的配置类
前面的配置类中指定用户名密码。从而达到登陆的作用。
下面就开始进行改造前面的配置类
修改配置类
@Configuration
public class configsecurity extends WebSecurityConfigurerAdapter {
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
这里表示设置用户名和密码
auth.inMemoryAuthentication().withUser("wsl").password("123").roles("admin");
通过密码编码器
// BCryptPasswordEncoder Password = new BCryptPasswordEncoder();
// String encode = password().encode("123");
// System.out.println("密码是:"+encode);
// auth.inMemoryAuthentication().withUser("wsl").password(encode).roles("admin");
//
// }
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
// 这里通过Spring的自定装配引入
// 该类表示的就是用户信息服务 待会会进行编写
// 认证先通过该类获取用户 账号和密码。在传给现在的这个类
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
}
UserDetailsServuce配置类
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Resource
private UsersDao usersDao;
@Override
// 这个方法中存在参数 该参数表示获取认证时候登录的 账号
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 通过用户名获取用户
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", s);
Users users = usersDao.selectOne(queryWrapper);
// 如果没有获取用户就返回异常
if (users == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 获取用户就将账号密码带入 传入出去
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
}
}
目录结构
测试
自定义登录页面
上面我们通过配置类,可以进行指定认证的账号和密码
那么下面我们要去指定认证的页面。和相关的配置
环境
首先我们现在操作的都属于web部分
所以我们引入boot-start-web
<!--引入SpringBoot的 web starter依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
默认页面就可以放入templates目录中了
配置类
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 配置认证
httpSecurity.formLogin()
.loginPage("/index") // 配置哪个 url 为登录页面
.loginProcessingUrl("/login") // 设置哪个是登录的 url。
.successForwardUrl("/success") // 登录成功之后跳转到哪个 url
.failureForwardUrl("/fail");// 登录失败之后跳转到哪个 url
httpSecurity.authorizeRequests()
.antMatchers("/layui/**","/index") //表示配置请求路径
.permitAll() // 指定 URL 无需保护。
.anyRequest() // 其他请求
.authenticated(); //需要认证
// 关闭 csrf
httpSecurity.csrf().disable();
}
控制器类
@Controller
public class HelloController {
@RequestMapping("show")
public String show(){
return "show";
}
@RequestMapping("index")
public String index(){
return "index";
}
@RequestMapping("success")
@ResponseBody
public String successSecurity(){
return "success";
}
@RequestMapping("fail")
@ResponseBody
public String failSecurity(){
return "fail";
}
@RequestMapping("layui")
@ResponseBody
public String layuiSecurity(){
return "layui";
}
@RequestMapping("other")
@ResponseBody
public String otherSecurity(){
return "other";
}
}
页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post" >
<!--这里的name属性一定要是这两个 因为在SpringSecurity底层就是指定之两个 name属性 -->
账号: <input type="text" name="username" >
密码: <input type="password" name="password" >
<button type="submit" >登录</button>
</form>
</body>
</html>
页面提交方式必须为 post 请求
所以上面的页面不能使用,用户名,密码必须为 username,password
结果
访问不需要认证的页面
访问需要认证的页面
会进行跳转到你设置的登录页面url
注意观察URL
认证之后可以访问
登录成功
url显示是login。却发现结果是指定的成功url 结果 页面
登录失败
url显示是login。却发现结果是指定的 失败 url 结果 页面
发现基本上都是HttpSecurity的API
HttpSecurity
方法 | 说明 |
---|---|
openidLogin() | 用于基于 OpenId 的验证 |
headers() | 将安全标头添加到响应 |
cors() | 配置跨域资源共享 ( CORS ) |
sessionManagement() | 允许配置会话管理 |
portMapper() | 允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 |
jee() | 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理 |
x509() | 配置基于x509的认证 |
rememberMe | 允许配置“记住我”的验证 |
authorizeRequests() | 允许基于使用HttpServletRequest限制访问 |
requestCache() | 允许配置请求缓存 |
exceptionHandling() | 允许配置错误处理 |
securityContext() | 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用 |
servletApi() | 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用 |
csrf() | 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用 |
logout() | 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success” |
anonymous() | 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS” |
formLogin() | 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面 |
oauth2Login() | 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证 |
requiresChannel() | 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射 |
httpBasic() | 配置 Http Basic 验证 |
addFilterAt() | 在指定的Filter类的位置添加过滤器 |
用户授权
security主要的两个概念,认证和权限。
认证:表示在进行访问页面前。进行一个登录操作
权限:表示你登录之后你可以进行哪些操作
主要就是在于几个方法的使用
hasAuthority
表示当前的主题具有指定的权限,才能进行访问否存就给出false
示例展示
// 这里指定的路径URL表示需要权限
.antMatchers("/hasAuthorityTest")
// 这里设定权限为admin
.hasAuthority("admin")
403异常
这里经过认证之后发现出现了403异常
是因为我们没有访问的权限。怎么才能有权限呢
在前面我们指定密码的Service类中设置过权限。
// 这里指定权限的
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
改变权限查看结果
HasAnyAuthority
上面的权限,一个请求只能设置一个权限。
该方法表示可以设置多个权限。满足任意一个权限
如何添加多个
HasRole
通过前面发现如果不能满足授权,在访问的时候将会出现403异常
403:权限不够
那么Role和前面有啥区别呢
对比源码
role的权限会在authority的前面追加字符 ROLE_
Authority
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
role
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 + "')";
}
}
操作
配置类
// 这里指定的路径URL表示需要权限
.antMatchers("/hasAuthorityTest")
这里设定权限为 admin 但是会在源码中转换为 ROLE_admin
.hasRole("admin")
service层
这里指定权限的
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_admin");
结果于上面一致
自定义403页面
403错误页面:表示因为没有权限不能进行访问
配置
配置类
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 指定当出现403的时候进行的页面跳转 URL
httpSecurity.exceptionHandling().accessDeniedPage("/unauth");
控制器
@GetMapping("/unauth")
public String accessDenyPage(){
return "unauth";
}
html
这里自己写吧!
注解使用
在学习Spring的时候我们就接触了很多注解。当然Security也存在很多注解可以使用
@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
启动类开启
@EnableGlobalMethodSecurity(securedEnabled=true)
控制器
控制器中指定该URL需要指定的 角色访问
// 测试注解:
@RequestMapping("testSecured")
@ResponseBody
@Secured({"ROLE_normal","ROLE_admin"})
public String helloUser() {
return "hello,user";
}
@PreAuthorize
与hasAuthorized方法差不多。在URL前进行权限限制
启动类
@EnableGlobalMethodSecurity(prePostEnabled = true)
控制器
@RequestMapping("/preAuthorize")
@ResponseBody
//@PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("preAuthorize");
return "preAuthorize";
}
@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值 的权限.
可以在控制器方法中填入打印语句。没有被授权后,打印语句却执行了
启动类
@EnableGlobalMethodSecurity(prePostEnabled = true)
控制器
@RequestMapping("/testPostAuthorize")
@ResponseBody
@PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("test--PostAuthorize");
return "PostAuthorize";
}
@PostFilter
权限验证之后对数据进行过滤 留下用户名是 admin1 的数据
@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
ArrayList<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1l,"admin1","6666"));
list.add(new UserInfo(2l,"admin2","888"));
return list;
}
@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;
}
用户注销
当我们的认证做完登录之后,在想去注销。如何操作呢
// 表示当进入该URL的时候就退出登录 。退出成功进入 /index请求
httpSecurity.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
List getAllUser(){
ArrayList list = new ArrayList<>();
list.add(new UserInfo(1l,“admin1”,“6666”));
list.add(new UserInfo(2l,“admin2”,“888”));
return list;
}
### @PreFilter
**@PreFilter: 进入控制器之前对数据进行过滤**
**这些都可以通过打印语句进行验证**
```java
@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;
}
用户注销
当我们的认证做完登录之后,在想去注销。如何操作呢
// 表示当进入该URL的时候就退出登录 。退出成功进入 /index请求
httpSecurity.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();