项目整体介绍
项目源码:https://github.com/xdouya/Spring-Security-demo/tree/master/03-mybatis-security
Spring Security整合Mybaits自定义查询实现认证鉴权,项目结构:
项目构建
数据表创建以及用户数据导入
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`username` varchar(50) NOT NULL,
`password` varchar(500) NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB;
INSERT IGNORE INTO `users` VALUES ('admin','{bcrypt}$2a$10$SAqQq0WEQYRA4etZpRa6e.Kew0sKKtC/ahFrSZXS1iHsy5EhZqLsa',1),('user','{bcrypt}$2a$10$SAqQq0WEQYRA4etZpRa6e.Kew0sKKtC/ahFrSZXS1iHsy5EhZqLsa',1),('vip','{bcrypt}$2a$10$SAqQq0WEQYRA4etZpRa6e.Kew0sKKtC/ahFrSZXS1iHsy5EhZqLsa',1);
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `ix_auth_username` (`username`,`authority`),
CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB;
INSERT IGNORE INTO `authorities` VALUES ('admin','ROLE_admin'),('user','ROLE_user'),('vip','ROLE_vip');
这里的这个users表和authorities表的字段没有强制要求,只要后续查询的时候能对应上就可以了;
Maven依赖
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
项目构建
- application.yml
spring:
application:
name: service-auth
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis-security?useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 'xdy088114'
mybatis:
mapper-locations: classpath:mapper/*.xml
- SecurityConfig
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置拦截器保护请求
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/vip/**").hasRole("vip")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and().formLogin()
.and().httpBasic();
}
/**
* 根据自动匹配密码编码器
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
- MyUserDetailServiceImpl,通过实现UserDetailsService#loadUserByUsername方法自定用户信息查询
/**
* 自定义UserDetailsService
* @author caiwl
* @date 2020/8/20 17:06
*/
@Service
public class MyUserDetailServiceImpl implements UserDetailsService {
private UserDao userDao;
@Autowired
public MyUserDetailServiceImpl(UserDao userDao){
this.userDao = userDao;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("整合mybatis 查询用户信息");
return userDao.loadUserByUsername(username);
- UserDao,用户数据访问接口
/**
* @author caiwl
* @date 2020/8/20 17:09
*/
public interface UserDao {
/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 用户信息
*/
UserPo loadUserByUsername(String username);
}
- UserMapper.xml, mapper文件
<?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="org.dy.security03.dao.UserDao">
<resultMap type="org.dy.security03.entiy.UserPo" id="UserMap">
<id column="username" property="username"/>
<result column="password" property="password"/>
<collection property="authorities" ofType="org.dy.security03.entiy.RolePo">
<id column="username" property="username"/>
<result column="authority" property="authority"/>
</collection>
</resultMap>
<select id="loadUserByUsername" resultMap="UserMap">
select
users.username, users.password, authorities.authority
from
users left join authorities on users.username = authorities.username
where users.username=#{username}
</select>
</mapper>
- UserPo,用户信息
/**
* @author caiwl
* @date 2020/8/20 17:08
*/
@Data
public class UserPo implements UserDetails, Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
private List<RolePo> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
- RolePo,角色信息
/**
* @author caiwl
* @date 2020/8/20 17:09
*/
@Data
public class RolePo implements GrantedAuthority {
private static final long serialVersionUID = 1L;
private String username;
private String authority;
@Override
public String getAuthority() {
return authority;
}
}
- HelloController,控制器接口
/**
* @author caiwl
* @date 2020/8/9 14:05
*/
@RestController
public class HelloController {
@GetMapping("/")
public String hello(){
return "hello, welcome";
}
@GetMapping("/admin/hello")
public String helloAdmin(){
return "hello admin";
}
@GetMapping("/vip/hello")
public String helloVip(){
return "hello vip";
}
@GetMapping("/user/hello")
public String helloUser(){
return "hello user";
}
}
项目测试
使用不同账号(账号admin或者user或者vip, 密码088114)访问http://localhost:8080/admin/hello、http://localhost:8080/user/hello、http://localhost:8080/vip/hello 来查看给个角色访问时的授权情况;
思考
基于数据库的认证鉴权方式,登录之后会产生一个session存储在内存之中,然后将sessionId返回给客户端,后续客户端请求资源时,会将sessionId携带上,那么服务端根据sessionId直接可以判断出用户的登录状态,如果用户登录过,则放行,如果没有登录,则会被拦截,跳转到登录页面重新登录,但这种将登录状态以及信息放在内存中的方式会带来两个问题:
- 由于session产生后放在服务器的内存之中,服务器因为某种原因(宕机或者更新)重启之后,则所有的session都会丢失,那么登录过的所有用户都需要重新登录;
- 分布式场景下,可能登录是在服务器A上,那么session保存在服务器A上,下一个请求,可能由服务器B处理,那么服务器B上没有这个session,就需要重新登录;
下一节,Spring Security(四)Spring Security分布式认证鉴权解决方案 解决以上两个问题;