Spring Boot 极简集成 Shiro

文章出处(转载自乐字节)

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 roleIds){

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,有任何问题可以随时来咨询我;感兴趣的朋友也可以进来学习下。

你可能感兴趣的:(Spring Boot 极简集成 Shiro)