文章出處(轉載自樂字節)
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<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. 系統返回狀態枚舉與包裝函數
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,有任何問題可以隨時來咨詢我;感興趣的朋友也可以進來學習下。