? ? ? ?【上篇】文章介绍了基于Jdbc的用户认证 & 授权,虽然实现了在数据库中认证和授权的逻辑,但是底层都是Spring Security底层帮我们定义好的,扩展性不强,企业开发中,常用的持久化方案是MyBatis Plus,那么Spring Security中如何定义基于MyBatis Plus的方式进行认证授权呢?请看下文详细介绍
<!-- 数据源配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.21</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
DROP TABLE IF EXISTS `mybatis_user`;
CREATE TABLE `mybatis_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '用户编号',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
`account_non_expired` int NOT NULL DEFAULT 1 COMMENT '账户是否没有过期(1:没有过期、0:已过期)',
`account_non_locked` int NOT NULL DEFAULT 1 COMMENT '账户是否没有被锁定(1:没有锁定、0:已锁定)',
`credentials_non_expired` int NOT NULL DEFAULT 1 COMMENT '密码是否没有过期(1:没有过期、0:已过期)',
`enabled` int NOT NULL DEFAULT 1 COMMENT '账户是否可用(1:可用、0:不可用)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
INSERT INTO `mybatis_user` VALUES (1, 'admin', '123456',1,1,1,1);
INSERT INTO `mybatis_user` VALUES (2, 'dba', '123456',1,1,1,1);
INSERT INTO `mybatis_user` VALUES (3, 'user', '123456',1,1,1,1);
DROP TABLE IF EXISTS `mybatis_role`;
CREATE TABLE `mybatis_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '角色编号',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称(英文)',
`name_zh` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称(中文)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
INSERT INTO `mybatis_role` VALUES (1, 'ROLE_admin', '超级管理员');
INSERT INTO `mybatis_role` VALUES (2, 'ROLE_dba', '数据库管理员');
INSERT INTO `mybatis_role` VALUES (3, 'ROLE_user', '普通用户');
DROP TABLE IF EXISTS `mybatis_user_role`;
CREATE TABLE `mybatis_user_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`u_id` int NULL DEFAULT NULL COMMENT '用户ID',
`r_id` int NULL DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色关联表' ROW_FORMAT = DYNAMIC;
INSERT INTO `mybatis_user_role` VALUES (1, 1, 1);
INSERT INTO `mybatis_user_role` VALUES (2, 1, 2);
INSERT INTO `mybatis_user_role` VALUES (3, 2, 2);
INSERT INTO `mybatis_user_role` VALUES (4, 3, 3);
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 16:19
* @Description:
*/
@TableName("mybatis_user")
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ToString
public class UserDO implements UserDetails {
/**
* 主键
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 账户是否没有过期
*/
private Boolean accountNonExpired;
/**
* 账户是否没有被锁定
*/
private Boolean accountNonLocked;
/**
* 密码是否没有过期
*/
private Boolean credentialsNonExpired;
/**
* 账户是否可用
*/
private Boolean enabled;
/**
* 角色
*/
private List<RoleDO> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (RoleDO role : getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getUsername() {
return username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String getPassword() {
return password;
}
public void setAccountNonExpired(Boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public void setAccountNonLocked(Boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Override
public boolean isEnabled() {
return enabled;
}
public List<RoleDO> getRoles() {
return roles;
}
public void setRoles(List<RoleDO> roles) {
this.roles = roles;
}
}
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 16:18
* @Description:
*/
@TableName("mybatis_role")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class RoleDO implements Serializable {
/**
* 角色id
*/
private Long id;
/**
* 角色名称(英文名称)
*/
private String name;
/**
* 角色名称(中文名称)
*/
private String nameZh;
}
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 16:20
* @Description:
*/
@TableName("mybatis_user_role")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class UserRoleDO implements Serializable {
/**
* 主键
*/
private Long id;
/**
* 用户id
*/
private Long uId;
/**
* 角色id
*/
private Long rId;
}
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 16:25
* @Description:
*/
public interface UserMapper extends BaseMapper<UserDO> {
/**
* 根据用户名查询用户
* @param username
* @return
*/
UserDO loadUserByUsername(String username);
}
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.star.mapper.UserMapper">
<resultMap id="BaseResultMap" type="userDO">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="accountNonExpired" column="account_non_expired"/>
<result property="accountNonLocked" column="account_non_locked"/>
<result property="credentialsNonExpired" column="credentials_non_expired"/>
<result property="enabled" column="enabled"/>
<collection property="roles" ofType="roleDO">
<id property="id" column="rId"/>
<result property="name" column="name"/>
<result property="nameZh" column="name_zh"/>
</collection>
</resultMap>
<select id="loadUserByUsername" resultMap="BaseResultMap">
select mu.id, mu.username, mu.password,mu.account_non_expired,mu.account_non_locked,mu.credentials_non_expired,mu.enabled,mr.`name`, mr.name_zh,mr.id as 'rId'
from mybatis_user mu
left join mybatis_user_role mur on mu.id = mur.u_id
left join mybatis_role mr on mr.id = mur.r_id
where mu.username = #{username}
</select>
</mapper>
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 16:33
* @Description:
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDO dbUserDO = userMapper.loadUserByUsername(username);
if (ObjectUtil.isNull(dbUserDO)) {
throw new UsernameNotFoundException("用户名不存在!");
}
return dbUserDO;
}
}
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/11 20:58
* @Description: 测试资源
*/
@RestController
public class HelloController7005 {
/**
* 任何人都可以访问
* @return
*/
@GetMapping("/helloWorld")
public R helloWorld() {
return R.ok().data("Hello World");
}
/**
* 登录后才能访问
* @return
*/
@GetMapping("/sayHi")
public R sayHi() {
return R.ok().data("嗨!");
}
/**
* 需要具有dba角色的人才能访问
* @return
*/
@GetMapping("/dba/helloWorld")
public R dba() {
return R.ok().data("dba Hello World");
}
/**
* 需要具有admin角色的人才能访问
* @return
*/
@GetMapping("/admin/helloWorld")
public R admin() {
return R.ok().data("admin Hello World");
}
}
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/11 21:50
* @Description: Spring Security配置类
*/
@Configuration
public class MyWebSecurityConfigurerAdapter7005 extends WebSecurityConfigurerAdapter {
@Resource
private MyAuthenticationSuccessHandler7005 successHandler;
@Resource
private MyAuthenticationFailureHandler7005 failureHandler;
@Resource
private MyLogoutSuccessHandler7005 logoutSuccessHandler;
@Resource
private MyAuthenticationEntryPoint7005 authenticationEntryPoint;
@Resource
private MyAccessDeniedHandler7005 accessDeniedHandler;
@Resource
private UserDetailsService userDetailsServiceImpl;
/**
* 密码加密器
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/**
* 定义基于MyBatis-Plus的用户
* @return
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl);
}
/**
* 角色继承
* @return
*/
@Bean
protected RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_admin > ROLE_dba");
return roleHierarchy;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/dba/**").hasRole("dba")
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/helloWorld")
.permitAll()
.anyRequest()
.authenticated()
.and()
/**
* 登录成功 & 登录失败回调
*/
.formLogin()
.loginPage("/login")
.successHandler(successHandler)
.failureHandler(failureHandler)
.and()
/**
* 注销登录回调
*/
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
.and()
.csrf()
.disable()
/**
* 未认证 & 权限不足回调
*/
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
}