文章目录
3. 登录流程源码分析
通过源码我们发现,InmermoryUserDetailManger中的loadUserByUsername方法最核心的。而且该类实现UserDetailService接口。 那么我们就可以自定义一个类并实现该接口UserDetailService。而且我们自定义的类也要交于容器来管理。
4. 模仿从数据库中获取用户
- 编写配置类
package com.ykq.springsecurity04.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
/**
* @program: springsecurity04
* @description:
* @author: 闫克起2
* @create: 2022-11-01 15:11
**/
@Configuration
public class MySpringSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//定义表单规则以及过滤规则
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//表单的规则
http.formLogin()
.loginPage("/login.html")
//该路径没有对应的控制层 交于springsecurity完成登录功能
.loginProcessingUrl("/login")
.successForwardUrl("/success")
.permitAll();
//禁用跨域伪造
http.csrf().disable();
//其他请求必须认证后才能访问
http.authorizeRequests().anyRequest().authenticated();
return http.build();
}
}
注意要将BCryptPasswordEncoder设置为bean,否者不能进行密码验证
- 自定义一个实现类实现UserDetailsService 接口,
package com.ykq.springsecurity04.service;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
/**
* @program: springsecurity04
* @description:
* @author: 闫克起2
* @create: 2022-11-01 15:18
**/
@Service //该类对象的生命周期交于spring容器管理
public class MyUserService implements UserDetailsService {
//注入 dao对象---根据账户查询用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*
String username, String password,
boolean enabled 是否可以, boolean accountNonExpired,账户是否没有过期
boolean credentialsNonExpired, 密码是否没有过期
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities: 当前账户具有的权限
*/
if("admin".equals(username)){
Collection<GrantedAuthority> authorities =new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("user:query"));
authorities.add(new SimpleGrantedAuthority("user:delete"));
authorities.add(new SimpleGrantedAuthority("user:update"));
authorities.add(new SimpleGrantedAuthority("user:insert"));
User user=new User(username,"$2a$10$k8XcVVIyNaXOfYR/q5v1veda/koy21JJ6FfGYQeRcubjuv1qax/v6",true,
true,true,true,authorities
);
return user;
}else if("ykq".equals(username)){
Collection<GrantedAuthority> authorities =new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("user:query"));
authorities.add(new SimpleGrantedAuthority("user:export"));
User user=new User(username,"$2a$10$k8XcVVIyNaXOfYR/q5v1veda/koy21JJ6FfGYQeRcubjuv1qax/v6",true,
true,true,true,authorities
);
return user;
}
return null;
}
}
- 从数据库中来获得用户十分简单,只需要实现UserDetailsService 接口,重写loadUserByUsername方法就行了,返回一个UserDetails 类型的数据。
- UserDetails 是一个接口,他有一个常用的实现类User,注意User是SpringSecurity包中的。
- 用户名和密码十分容易获取,而对于权限信息authorities需要我们想办法从数据库中封装了。
5. 常见授权认证数据库表的设计
综合案例
使用springboot + thymeleaf+ mybatis+ druid+springsecurity模拟一下从数据库中查询用户,完成授权认证
前后端不分离
1. 引入jar包
<dependencies>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--数据库连接池druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!--mybatis和springboot整合的依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--thymeleaf和springsecurity的扩展包-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--security的启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--hutool工具-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置application.yml文件
mybatis:
configuration:
# 开启mybatis日志输出(可以打印出sql执行的日志)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# mybatis的mapper扫描
mapper-locations: classpath:/mapper/*.xml
spring:
# 数据源
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 5
max-active: 20
max-wait: 3000
min-idle: 5
password: 1234
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=Asia/Shanghai
username: root
server:
port: 80
3. 数据库层
- 首先创建用户实体类
package com.aaa.springsecuritydemo02.entity;
import lombok.Data;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/11/1 22:40
* @description :
*/
@Data
public class Users {
private Integer userid;
private String username;
private String userpwd;
private String sex;
private String address;
}
为了和security自带的User区分,这里将我们自己的实体类名字叫Users
- 创建UserDao及UserDao.xml
@Mapper
public interface UsersDao {
Users getUserByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aaa.springsecuritydemo02.dao.UsersDao">
<select id="getUserByUsername" resultType="com.aaa.springsecuritydemo02.entity.Users">
select *
from user
where username = #{username}
</select>
</mapper>
- PermissonDao和PermissonDao.xml
返回UserDetails 的时候需要权限信息,写这个接口是为了根据用户Id查询权限消息
Permisson实体类
@Data
public class Permission {
private Integer id;
private String pername;
private String percode;
}
PermissionDao 接口
@Mapper
public interface PermissionDao {
/**
* 根据用户id查询对应的权限
*/
List<Permission> findPermissionByUserid(Integer userid);
}
PermissionDao.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aaa.springsecuritydemo02.dao.PermissionDao">
<select id="findPermissionByUserid" resultType="com.aaa.springsecuritydemo02.entity.Permission">
select distinct p.*
from user_role ur
join role_permission rp
on ur.roleid = rp.roleid
join permission p on rp.perid = p.perid
where ur.userid = #{userid}
</select>
</mapper>
注意sql语句
4. service层
package com.aaa.springsecuritydemo02.service;
import com.aaa.springsecuritydemo02.dao.PermissionDao;
import com.aaa.springsecuritydemo02.dao.UsersDao;
import com.aaa.springsecuritydemo02.entity.Permission;
import com.aaa.springsecuritydemo02.entity.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/11/1 15:41
* @description :
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersDao usersDao;
@Autowired
private PermissionDao permissionDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = usersDao.getUserByUsername(username);
if (user != null) {
Collection<GrantedAuthority> authorities = new ArrayList<>();
List<Permission> permissions = permissionDao.findPermissionByUserid(user.getUserid());
//把上面的权限封装authorities中----jdk1.8 Stream流--
//authorities=permissions.stream()
// .map(item->new SimpleGrantedAuthority(item.getPercode()))
// .collect(Collectors.toList());
for (Permission item : permissions) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(item.getPercode());
authorities.add(simpleGrantedAuthority);
}
return new User(username, user.getUserpwd(), authorities);
}
return null;
}
}
注意要交给Spring管理
@Service
5. SecurityConfig配置类
package com.aaa.springsecuritydemo02.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/10/29 15:49
* @description :
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//认证
http
.formLogin()
//默认登录页面
.loginPage("/login.html")
//登录的处理路径,无需自己创建该路径的业务处理功能。
.loginProcessingUrl("/login")
//通过这两个可以修改表单中name的值
//登录成功跳转路径
.successForwardUrl("/user/success")
//放行
.permitAll();
//授权
http
//禁用跨域请求伪造 csrf
.csrf().disable();
http
.authorizeRequests()
//其他的请求都需要认证
.anyRequest().authenticated();
return http.build();
}
}
配置文件要配置密码加密器,否者程序不知道要用那种方式来匹配密码
6. 控制层
package com.aaa.springsecuritydemo02.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.security.Principal;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/10/31 14:48
* @description :
*/
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 认证成功后,会把用户的信息封装到Authentication该类中,
* 并且该类存放在SecurityContext对象中---等价于session
* @return Authentication
*/
@GetMapping("/info")
public Authentication authentication() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
//通过Authentication通常可以拿到Principal,里面存放着用户信息
//Object principal = authentication.getPrincipal();
//System.out.println(principal);
return authentication;
}
@PostMapping("/success")
public ModelAndView success(ModelAndView modelAndView){
modelAndView.setViewName("success");
return modelAndView;
}
}
7. 页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>登录成功页面</title>
</head>
<body>
<a href="#" sec:authorize="hasAuthority('/user/query')">查询用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/delete')">删除用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/update')">修改用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/insert')">添加用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/export')">导出用户</a><br>
</body>
</html>
- 注意thymeleaf中要想使用sec语法需要引入thymeleaf-extras-springsecurity5的jar包,页面头部写的
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
只是来规定语法,不让报错并能写代码时给出提示,并没有实际作用。真正起作用的是thymeleaf-extras-springsecurity5包。- 其实不写头也可以,只是没有语法提示,可能页面会报错,但实际运行不影响