文章出处(转载自乐字节)
1. 前言
Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。
Shiro有三大核心组件:
Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,与当前应用交互的任何东西都是Subject,如网络爬虫等。所有的Subject都要绑定到SecurityManager上,与Subject的交互实际上是被转换为与SecurityManager的交互。
SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。作用类似于SpringMVC中的DispatcherServlet,用于拦截所有请求并进行处理。
Realm:Realm是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realm来自定义的管理我们自己系统内部的权限规则。SecurityManager要验证用户,需要从Realm中获取用户。可以把Realm看做是数据源。
2. 数据库设计
2.1 User(用户)
SETNAMESutf8mb4;
SETFOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROPTABLEIFEXISTS`user`;
CREATETABLE`user`(
`id`bigint(20) NOTNULLAUTO_INCREMENT,
`password`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULL,
`username`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULL,
`account`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULL,
PRIMARY KEY(`id`) USINGBTREE
) ENGINE= MyISAM AUTO_INCREMENT = 4CHARACTERSET= utf8 COLLATE= utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERTINTO`user`VALUES(1, 'root', '超级用户', 'root');
INSERTINTO`user`VALUES(2, 'user', '普通用户', 'user');
INSERTINTO`user`VALUES(3, 'vip', 'VIP用户', 'vip');
SETFOREIGN_KEY_CHECKS = 1;
2.2 Role(角色)
SETNAMESutf8mb4;
SETFOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROPTABLEIFEXISTS`role`;
CREATETABLE`role`(
`id`int(11) NOTNULLAUTO_INCREMENT,
`role`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULL,
`desc`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULL,
PRIMARY KEY(`id`) USINGBTREE
) ENGINE= MyISAM AUTO_INCREMENT = 4CHARACTERSET= utf8 COLLATE= utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERTINTO`role`VALUES(1, 'admin', '超级管理员');
INSERTINTO`role`VALUES(2, 'user', '普通用户');
INSERTINTO`role`VALUES(3, 'vip_user', 'VIP用户');
SETFOREIGN_KEY_CHECKS = 1;
2.3 Permission(权限)
SETNAMESutf8mb4;
SETFOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROPTABLEIFEXISTS`permission`;
CREATETABLE`permission`(
`id`int(11) NOTNULLAUTO_INCREMENT,
`permission`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULLCOMMENT'权限名称',
`desc`varchar(255) CHARACTERSETutf8 COLLATEutf8_general_ci NULLDEFAULTNULLCOMMENT'权限描述',
PRIMARY KEY(`id`) USINGBTREE
) ENGINE= MyISAM AUTO_INCREMENT = 5CHARACTERSET= utf8 COLLATE= utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERTINTO`permission`VALUES(1, 'add', '增加');
INSERTINTO`permission`VALUES(2, 'update', '更新');
INSERTINTO`permission`VALUES(3, 'select', '查看');
INSERTINTO`permission`VALUES(4, 'delete', '删除');
SETFOREIGN_KEY_CHECKS = 1;
2.4 User_Role(用户-角色)
SETNAMESutf8mb4;
SETFOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROPTABLEIFEXISTS`user_role`;
CREATETABLE`user_role`(
`id`int(11) NOTNULLAUTO_INCREMENT,
`user_id`int(11) NULLDEFAULTNULL,
`role_id`int(11) NULLDEFAULTNULL,
PRIMARY KEY(`id`) USINGBTREE
) ENGINE= MyISAM AUTO_INCREMENT = 4CHARACTERSET= utf8 COLLATE= utf8_general_ci ROW_FORMAT = Fixed;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERTINTO`user_role`VALUES(1, 1, 1);
INSERTINTO`user_role`VALUES(2, 2, 2);
INSERTINTO`user_role`VALUES(3, 3, 3);
SETFOREIGN_KEY_CHECKS = 1;
2.5 Role_Permission(角色-权限)
SETNAMESutf8mb4;
SETFOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROPTABLEIFEXISTS`role_permission`;
CREATETABLE`role_permission`(
`id`int(11) NOTNULLAUTO_INCREMENT,
`role_id`int(11) NULLDEFAULTNULL,
`permission_id`int(255) NULLDEFAULTNULL,
PRIMARY KEY(`id`) USINGBTREE
) ENGINE= MyISAM AUTO_INCREMENT = 9CHARACTERSET= utf8 COLLATE= utf8_general_ci ROW_FORMAT = Fixed;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERTINTO`role_permission`VALUES(1, 1, 1);
INSERTINTO`role_permission`VALUES(2, 1, 2);
INSERTINTO`role_permission`VALUES(3, 1, 3);
INSERTINTO`role_permission`VALUES(4, 1, 4);
INSERTINTO`role_permission`VALUES(5, 2, 3);
INSERTINTO`role_permission`VALUES(6, 3, 3);
INSERTINTO`role_permission`VALUES(7, 3, 2);
INSERTINTO`role_permission`VALUES(8, 2, 1);
SETFOREIGN_KEY_CHECKS = 1;
3. 项目结构
4. 前期准备
4.1 导入Pom
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
org.apache.shiro
shiro-spring
1.4.0
4.2 application.yml
server:
port: 8903
spring:
application:
name: lab-user
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/laboratory?charset=utf8
username: root
password: root
mybatis:
type-aliases-package: cn.ntshare.laboratory.entity
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
4.3 实体类
4.3.1 User.java
@Data
@ToString
publicclassUserimplementsSerializable{
privatestaticfinallongserialVersionUID = -6056125703075132981L;
privateInteger id;
privateString account;
privateString password;
privateString username;
}
4.3.2 Role.java
@Data
@ToString
publicclassRoleimplementsSerializable{
privatestaticfinallongserialVersionUID = -1767327914553823741L;
privateInteger id;
privateString role;
privateString desc;
}
4.4 Dao层
4.4.1 PermissionMapper.java
@Mapper
@Repository
publicinterfacePermissionMapper {
List findByRoleId(@Param("roleIds") List roleIds);
}
4.4.2 PermissionMapper.xml
id, permission, desc
selectpermission
frompermission, role_permission rp
whererp.permission_id = permission.id and rp.role_id in
#{id}
4.4.3 RoleMapper.java
@Mapper
@Repository
publicinterfaceRoleMapper {
List findRoleByUserId(@Param("userId") Integer userId);
}
4.4.4 RoleMapper.xml
id, user_id, role_id
selectrole.id, role
fromrole, user, user_role ur
whererole.id = ur.role_id and ur.user_id = user.id and user.id = #{userId}
4.4.5 UserMapper.java
@Mapper
@Repository
public interface UserMapper {
UserfindByAccount(@Param("account") String account);
}
4.4.6 UserMapper.xml
id, account, password, username
select
fromuser
whereaccount = #{account}
4.5 Service层
4.5.1 PermissionServiceImpl.java
@Service
publicclassPermissionServiceImplimplementsPermissionService{
@Autowired
privatePermissionMapper permissionMapper;
@Override
publicList findByRoleId(List
returnpermissionMapper.findByRoleId(roleIds);
}
}
4.5.2 RoleServiceImpl.java
@Service
publicclassRoleServiceImplimplementsRoleService{
@Autowired
privateRoleMapper roleMapper;
@Override
publicList findRoleByUserId(Integer id){
returnroleMapper.findRoleByUserId(id);
}
}
4.5.3 UserServiceImpl.java
@Service
publicclassUserServiceImplimplementsUserService{
@Autowired
privateUserMapper userMapper;
@Override
publicUser findByAccount(String account){
returnuserMapper.findByAccount(account);
}
}
4.6. 系统返回状态枚举与包装函数
4.6.1 ServerResponseEnum.java
@AllArgsConstructor
@Getter
public enum ServerResponseEnum {
SUCCESS(0, "成功"),
ERROR(10, "失败"),
ACCOUNT_NOT_EXIST(11, "账号不存在"),
DUPLICATE_ACCOUNT(12, "账号重复"),
ACCOUNT_IS_DISABLED(13, "账号被禁用"),
INCORRECT_CREDENTIALS(14, "账号或密码错误"),
NOT_LOGIN_IN(15, "账号未登录"),
UNAUTHORIZED(16, "没有权限")
;
Integercode;
Stringmessage;
}
4.6.2 ServerResponseVO.java
@Getter
@Setter
@NoArgsConstructor
publicclassServerResponseVO implementsSerializable{
privatestaticfinallongserialVersionUID = -1005863670741860901L;
// 响应码
privateInteger code;
// 描述信息
privateString message;
// 响应内容
privateT data;
privateServerResponseVO(ServerResponseEnum responseCode){
this.code = responseCode.getCode();
this.message = responseCode.getMessage();
}
privateServerResponseVO(ServerResponseEnum responseCode, T data){
this.code = responseCode.getCode();
this.message = responseCode.getMessage();
this.data = data;
}
privateServerResponseVO(Integer code, String message){
this.code = code;
this.message = message;
}
/**
* 返回成功信息
* @paramdata 信息内容
* @param
* @return
*/
publicstatic ServerResponseVO success(T data){
returnnewServerResponseVO<>(ServerResponseEnum.SUCCESS, data);
}
/**
* 返回成功信息
* @return
*/
publicstaticServerResponseVO success(){
returnnewServerResponseVO(ServerResponseEnum.SUCCESS);
}
/**
* 返回错误信息
* @paramresponseCode 响应码
* @return
*/
publicstaticServerResponseVO error(ServerResponseEnum responseCode){
returnnewServerResponseVO(responseCode);
}
}
4.7 统一异常处理
当用户身份认证失败时,会抛出UnauthorizedException,我们可以通过统一异常处理来处理该异常
@RestControllerAdvice
public class UserExceptionHandler {
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ServerResponseVO UnAuthorizedExceptionHandler(UnauthorizedException e) {
returnServerResponseVO.error(ServerResponseEnum.UNAUTHORIZED);
}
}
5. 集成Shiro
5.1 UserRealm.java
/**
* 负责认证用户身份和对用户进行授权
*/
publicclassUserRealmextendsAuthorizingRealm{
@Autowired
privateUserService userService;
@Autowired
privateRoleService roleService;
@Autowired
privatePermissionService permissionService;
// 用户授权
protectedAuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
User user = (User) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = newSimpleAuthorizationInfo();
List roleList = roleService.findRoleByUserId(user.getId());
Set roleSet = newHashSet<>();
List roleIds = newArrayList<>();
for(Role role : roleList) {
roleSet.add(role.getRole());
roleIds.add(role.getId());
}
// 放入角色信息
authorizationInfo.setRoles(roleSet);
// 放入权限信息
List permissionList = permissionService.findByRoleId(roleIds);
authorizationInfo.setStringPermissions(newHashSet<>(permissionList));
returnauthorizationInfo;
}
// 用户认证
protectedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken)throwsAuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authToken;
User user = userService.findByAccount(token.getUsername());
if(user == null) {
returnnull;
}
returnnewSimpleAuthenticationInfo(user, user.getPassword(), getName());
}
}
5.2 ShiroConfig.java
@Configuration
publicclassShiroConfig{
@Bean
publicUserRealm userRealm(){
returnnewUserRealm();
}
@Bean
publicDefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager();
securityManager.setRealm(userRealm());
returnsecurityManager;
}
/**
* 路径过滤规则
* @return
*/
@Bean
publicShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = newShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
Map map = newLinkedHashMap<>();
// 有先后顺序
map.put("/login", "anon"); // 允许匿名访问
map.put("/**", "authc"); // 进行身份认证后才能访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
returnshiroFilterFactoryBean;
}
/**
* 开启Shiro注解模式,可以在Controller中的方法上添加注解
* @paramsecurityManager
* @return
*/
@Bean
publicAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager")DefaultSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = newAuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
returnauthorizationAttributeSourceAdvisor;
}
5.3 LoginController.java
@RestController
@RequestMapping("")
publicclassLoginController {
@PostMapping("/login")
publicServerResponseVO login(@RequestParam(value = "account") Stringaccount,
@RequestParam(value = "password") Stringpassword) {
Subject userSubject = SecurityUtils.getSubject();
UsernamePasswordToken token = newUsernamePasswordToken(account, password);
try{
// 登录验证
userSubject.login(token);
returnServerResponseVO.success();
} catch(UnknownAccountException e) {
returnServerResponseVO.error(ServerResponseEnum.ACCOUNT_NOT_EXIST);
} catch(DisabledAccountException e) {
returnServerResponseVO.error(ServerResponseEnum.ACCOUNT_IS_DISABLED);
} catch(IncorrectCredentialsException e) {
returnServerResponseVO.error(ServerResponseEnum.INCORRECT_CREDENTIALS);
} catch(Throwable e) {
e.printStackTrace();
returnServerResponseVO.error(ServerResponseEnum.ERROR);
}
}
@GetMapping("/login")
publicServerResponseVO login() {
returnServerResponseVO.error(ServerResponseEnum.NOT_LOGIN_IN);
}
@GetMapping("/auth")
publicStringauth() {
return"已成功登录";
}
@GetMapping("/role")
@RequiresRoles("vip")
publicStringrole() {
return"测试Vip角色";
}
@GetMapping("/permission")
@RequiresPermissions(value = {"add", "update"}, logical = Logical.AND)
publicStringpermission() {
return"测试Add和Update权限";
}
}
6. 测试
6.1 用root用户登录
6.1.1 登录
6.1.2 验证是否登录
6.1.3 测试角色权限
6.1.4 测试用户操作权限
7. 总结
本文演示了 Spring Boot 极简集成 Shiro 框架,实现了基础的身份认证和授权功能,如有不足,请多指教。
后续可扩展的功能点有:
1. 集成 Redis 实现 Shiro 的分布式会话
2. 集成 JWT 实现单点登录功能
如果看到这里,说明你喜欢这篇文章,761935205这是我的java无广告交流裙,进裙暗号Z17,有任何问题可以随时来咨询我;感兴趣的朋友也可以进来学习下。