文章出處(轉(zhuǎn)載自樂(lè)字節(jié))
1. 前言
Apache Shiro是一個(gè)功能強(qiáng)大且易于使用的Java安全框架辣之,提供了認(rèn)證,授權(quán)皱炉,加密怀估,和會(huì)話管理。
Shiro有三大核心組件:
Subject:即當(dāng)前用戶合搅,在權(quán)限管理的應(yīng)用程序里往往需要知道誰(shuí)能夠操作什么多搀,誰(shuí)擁有操作該程序的權(quán)利,shiro中則需要通過(guò)Subject來(lái)提供基礎(chǔ)的當(dāng)前用戶信息历筝,Subject 不僅僅代表某個(gè)用戶酗昼,與當(dāng)前應(yīng)用交互的任何東西都是Subject廊谓,如網(wǎng)絡(luò)爬蟲(chóng)等梳猪。所有的Subject都要綁定到SecurityManager上,與Subject的交互實(shí)際上是被轉(zhuǎn)換為與SecurityManager的交互蒸痹。
SecurityManager:即所有Subject的管理者春弥,這是Shiro框架的核心組件,可以把他看做是一個(gè)Shiro框架的全局管理組件叠荠,用于調(diào)度各種Shiro框架的服務(wù)匿沛。作用類(lèi)似于SpringMVC中的DispatcherServlet,用于攔截所有請(qǐng)求并進(jìn)行處理榛鼎。
Realm:Realm是用戶的信息認(rèn)證器和用戶的權(quán)限人證器逃呼,我們需要自己來(lái)實(shí)現(xiàn)Realm來(lái)自定義的管理我們自己系統(tǒng)內(nèi)部的權(quán)限規(guī)則鳖孤。SecurityManager要驗(yàn)證用戶,需要從Realm中獲取用戶抡笼∷沾В可以把Realm看做是數(shù)據(jù)源。
2. 數(shù)據(jù)庫(kù)設(shè)計(jì)
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',?'超級(jí)用戶',?'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',?'超級(jí)管理員');
INSERTINTO`role`VALUES(2,?'user',?'普通用戶');
INSERTINTO`role`VALUES(3,?'vip_user',?'VIP用戶');
SETFOREIGN_KEY_CHECKS =?1;
2.3 Permission(權(quán)限)
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'權(quán)限名稱',
`desc`varchar(255)?CHARACTERSETutf8?COLLATEutf8_general_ci?NULLDEFAULTNULLCOMMENT'權(quán)限描述',
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(角色-權(quán)限)
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. 項(xiàng)目結(jié)構(gòu)
4. 前期準(zhǔn)備
4.1 導(dǎo)入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 實(shí)體類(lèi)
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<Integer> 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. 系統(tǒng)返回狀態(tài)枚舉與包裝函數(shù)
4.6.1 ServerResponseEnum.java
@AllArgsConstructor
@Getter
public enum ServerResponseEnum {
SUCCESS(0,?"成功"),
ERROR(10,?"失敗"),
ACCOUNT_NOT_EXIST(11,?"賬號(hào)不存在"),
DUPLICATE_ACCOUNT(12,?"賬號(hào)重復(fù)"),
ACCOUNT_IS_DISABLED(13,?"賬號(hào)被禁用"),
INCORRECT_CREDENTIALS(14,?"賬號(hào)或密碼錯(cuò)誤"),
NOT_LOGIN_IN(15,?"賬號(hào)未登錄"),
UNAUTHORIZED(16,?"沒(méi)有權(quán)限")
;
Integercode;
Stringmessage;
}
4.6.2 ServerResponseVO.java
@Getter
@Setter
@NoArgsConstructor
publicclassServerResponseVO?implementsSerializable{
privatestaticfinallongserialVersionUID = -1005863670741860901L;
// 響應(yīng)碼
privateInteger code;
// 描述信息
privateString message;
// 響應(yīng)內(nèi)容
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 信息內(nèi)容
*?@param
*?@return
*/
publicstatic ServerResponseVO?success(T data){
returnnewServerResponseVO<>(ServerResponseEnum.SUCCESS, data);
}
/**
* 返回成功信息
*?@return
*/
publicstaticServerResponseVO?success(){
returnnewServerResponseVO(ServerResponseEnum.SUCCESS);
}
/**
* 返回錯(cuò)誤信息
*?@paramresponseCode 響應(yīng)碼
*?@return
*/
publicstaticServerResponseVO?error(ServerResponseEnum responseCode){
returnnewServerResponseVO(responseCode);
}
}
4.7 統(tǒng)一異常處理
當(dāng)用戶身份認(rèn)證失敗時(shí)推姻,會(huì)拋出UnauthorizedException平匈,我們可以通過(guò)統(tǒng)一異常處理來(lái)處理該異常
@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
/**
* 負(fù)責(zé)認(rèn)證用戶身份和對(duì)用戶進(jìn)行授權(quán)
*/
publicclassUserRealmextendsAuthorizingRealm{
@Autowired
privateUserService userService;
@Autowired
privateRoleService roleService;
@Autowired
privatePermissionService permissionService;
// 用戶授權(quán)
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);
// 放入權(quán)限信息
List permissionList = permissionService.findByRoleId(roleIds);
authorizationInfo.setStringPermissions(newHashSet<>(permissionList));
returnauthorizationInfo;
}
// 用戶認(rèn)證
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;
}
/**
* 路徑過(guò)濾規(guī)則
*?@return
*/
@Bean
publicShiroFilterFactoryBean?shiroFilter(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean =?newShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
Map map =?newLinkedHashMap<>();
// 有先后順序
map.put("/login",?"anon");?// 允許匿名訪問(wèn)
map.put("/**",?"authc");?// 進(jìn)行身份認(rèn)證后才能訪問(wèn)
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
returnshiroFilterFactoryBean;
}
/**
* 開(kāi)啟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{
// 登錄驗(yàn)證
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"測(cè)試Vip角色";
}
@GetMapping("/permission")
@RequiresPermissions(value = {"add",?"update"}, logical = Logical.AND)
publicStringpermission() {
return"測(cè)試Add和Update權(quán)限";
}
}
6. 測(cè)試
6.1 用root用戶登錄
6.1.1 登錄
6.1.2 驗(yàn)證是否登錄
6.1.3 測(cè)試角色權(quán)限
6.1.4 測(cè)試用戶操作權(quán)限
7. 總結(jié)
本文演示了 Spring Boot?極簡(jiǎn)集成 Shiro 框架藏古,實(shí)現(xiàn)了基礎(chǔ)的身份認(rèn)證和授權(quán)功能增炭,如有不足,請(qǐng)多指教拧晕。
后續(xù)可擴(kuò)展的功能點(diǎn)有:
1. 集成 Redis 實(shí)現(xiàn) Shiro 的分布式會(huì)話
2. 集成 JWT 實(shí)現(xiàn)單點(diǎn)登錄功能
如果看到這里隙姿,說(shuō)明你喜歡這篇文章,761935205這是我的java無(wú)廣告交流裙厂捞,進(jìn)裙暗號(hào)Z17孟辑,有任何問(wèn)題可以隨時(shí)來(lái)咨詢我;感興趣的朋友也可以進(jìn)來(lái)學(xué)習(xí)下蔫敲。