從零搭建自己的SpringBoot後臺框架(十四)

Mr_初晨發表於2018-05-26
Hello大家好,本章我們新增shiro許可權保護介面功能 。有問題可以聯絡我mr_beany@163.com。另求各路大神指點,感謝

一:什麼是shiro

Shiro是一個Java平臺的開源許可權框架,用於認證和訪問授權。具體來說,滿足對如下元素的支援:

  • 使用者,角色,許可權(僅僅是操作許可權,資料許可權必須與業務需求緊密結合),資源(url)。
  • 使用者分配角色,角色定義許可權。
  • 訪問授權時支援角色或者許可權,並且支援多級的許可權定義。

簡單來說shiro是通過自己的配置,來保證介面只能被指定的角色或許可權訪問,保證了介面的安全。

二:新增shiro依賴

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring-boot-web-starter</artifactId>
   <version>1.4.0-RC2</version>
</dependency>複製程式碼

三:建立許可權,角色,使用者角色許可權關係表等資料庫表

1:修改原有userInfo

新增password(使用者密碼,經過加密之後的),salt(加密鹽值)

修改之後結構為下圖

從零搭建自己的SpringBoot後臺框架(十四)

其中password資料為123456加密之後生成的

2:新增角色表

CREATE TABLE `sys_role` (
  `id` varchar(36) NOT NULL COMMENT '角色名稱',
  `role_name` varchar(255) DEFAULT NULL COMMENT '角色名稱,用於顯示',
  `role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述',
  `role_value` varchar(255) DEFAULT NULL COMMENT '角色值,用於許可權判斷',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_disable` int(1) DEFAULT NULL COMMENT '是否禁用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';複製程式碼

3:新增使用者角色關係表

CREATE TABLE `user_role` (
  `id` varchar(36) NOT NULL,
  `user_id` varchar(36) DEFAULT NULL COMMENT '使用者ID',
  `role_id` varchar(36) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='使用者角色關係表';
複製程式碼

4:新增許可權表

CREATE TABLE `sys_perm` (
  `id` varchar(36) NOT NULL,
  `perm_name` varchar(255) DEFAULT NULL COMMENT '許可權名稱',
  `perm_desc` varchar(255) DEFAULT NULL COMMENT '許可權描述',
  `perm_value` varchar(255) DEFAULT NULL COMMENT '許可權值',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_disable` int(1) DEFAULT NULL COMMENT '是否禁用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;複製程式碼

5:新增角色許可權表

CREATE TABLE `role_perm` (
  `id` varchar(36) NOT NULL,
  `perm_id` varchar(32) DEFAULT NULL COMMENT '許可權id',
  `role_id` varchar(32) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色許可權表';複製程式碼

表關係大致為下圖,一個使用者對應多個角色,一個角色對用多個許可權

原諒我辣雞的畫圖

從零搭建自己的SpringBoot後臺框架(十四)

四:利用程式碼生成器生成四張新表的mapper,dao,service和controller

大家直接生成就可以,由於程式碼過多,這裡不做展示,詳情可以去碼雲上參考

五:新增查詢角色和許可權的方法

UserRoleMapper.xml

<!--根據使用者id查詢該使用者所有角色-->
<select id="getRolesByUserId" resultType="string" parameterType="string">
  select sr.role_value
  from user_role ur
  left join sys_role sr on ur.role_id = sr.id
  where ur.user_id = #{userId,jdbcType=VARCHAR}
  and sr.is_disable = 0
</select>複製程式碼

UserRoleMapper.java

List<String> getRolesByUserId(String userId);複製程式碼

RolePermMapper.xml

<select id="getPermsByUserId" resultType="string" parameterType="string">
  select distinct
      p.perm_value
  from
      sys_perm p,
      role_perm rp,
      user_role ur
  where
      p.id = rp.perm_id
      and ur.role_id = rp.role_id
      and ur.user_id = #{userId,jdbcType=VARCHAR}
      and p.is_disable = 0
</select>複製程式碼

說明,這裡查詢出該使用者對應的所有許可權,由於角色與許可權是多對多的關係,所以查詢出的使用者的許可權可能會有重複,需要distinct去重。

RolePermMapper.java

List<String> getPermsByUserId(String userId);複製程式碼

修改userInfo實體類為如下

package com.example.demo.model;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.util.HashSet;
import java.util.Set;

/**
 * @author 張瑤
 * @Description:
 * @time 2018/4/18 11:55
 */
public class UserInfo {

    /**
     * 主鍵
     */
    @Id
    private String id;

    /**
     * 使用者名稱
     */
    @Column(name = "user_name")
    private String userName;

    private String password;

    /**
     * 加密鹽值
     */
    private String salt;

    /**
     * 使用者所有角色值,用於shiro做角色許可權的判斷
     */
    @Transient
    private Set<String> roles;

    /**
     * 使用者所有許可權值,用於shiro做資源許可權的判斷
     */
    @Transient
    private Set<String> perms;



    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<String> getRoles() {
        return roles;
    }

    public void setRoles(Set<String> roles) {
        this.roles = roles;
    }

    public Set<String> getPerms() {
        return perms;
    }

    public void setPerms(Set<String> perms) {
        this.perms = perms;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}
複製程式碼

六:自定義Realm

建立core→shiro→CustomRealm.java

1:登入認證實現

在Shiro中,最終是通過Realm來獲取應用程式中的使用者、角色及許可權資訊的。通常情況下,在Realm中會直接從我們的資料來源中獲取Shiro需要的驗證資訊。可以說,Realm是專用於安全框架的DAO.
Shiro的認證過程最終會交由Realm執行,這時會呼叫Realm的getAuthenticationInfo(token)方法。

該方法主要執行以下操作:

  • 檢查提交的進行認證的令牌資訊
  • 根據令牌資訊從資料來源(通常為資料庫)中獲取使用者資訊
  • 對使用者資訊進行匹配驗證。
  • 驗證通過將返回一個封裝了使用者資訊的AuthenticationInfo例項。
  • 驗證失敗則丟擲AuthenticationException異常資訊。

而在我們的應用程式中要做的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,過載doGetAuthenticationInfo(),重寫獲取使用者資訊的方法。

2:連結許可權的實現

過載doGetAuthorizationInfo(),來定義如何獲取使用者的角色和許可權的邏輯,給shiro做許可權判斷

完整程式碼如下

package com.example.demo.core.shiro;


import com.example.demo.model.UserInfo;
import com.example.demo.service.RolePermService;
import com.example.demo.service.UserInfoService;
import com.example.demo.service.UserRoleService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定義如何查詢使用者資訊,如何查詢使用者的角色和許可權,如何校驗密碼等邏輯
 */
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserInfoService userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RolePermService rolePermService;

    /**
     * 告訴shiro如何根據獲取到的使用者資訊中的密碼和鹽值來校驗密碼
     */
    {
        //設定用於匹配密碼的CredentialsMatcher
        HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher();
        hashMatcher.setHashAlgorithmName("md5");
        hashMatcher.setStoredCredentialsHexEncoded(true);
        //加密的次數
        hashMatcher.setHashIterations(1024);
        this.setCredentialsMatcher(hashMatcher);
    }


    /**
     *  定義如何獲取使用者的角色和許可權的邏輯,給shiro做許可權判斷
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        UserInfo user = (UserInfo) getAvailablePrincipal(principals);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(user.getRoles());
        info.setStringPermissions(user.getPerms());
        return info;
    }

    /**
     * 定義如何獲取使用者資訊的業務邏輯,給shiro做登入
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }
        UserInfo userDB = userService.selectBy("userName",username);
        if (userDB == null) {
            throw new UnknownAccountException("No account found for admin [" + username + "]");
        }
        //查詢使用者的角色和許可權存到SimpleAuthenticationInfo中,這樣在其它地方
        //SecurityUtils.getSubject().getPrincipal()就能拿出使用者的所有資訊,包括角色和許可權
        List<String> roleList = userRoleService.getRolesByUserId(userDB.getId());
        List<String> permList = rolePermService.getPermsByUserId(userDB.getId());
        Set<String> roles = new HashSet(roleList);
        Set<String> perms = new HashSet(permList);
        userDB.setRoles(roles);
        userDB.setPerms(perms);

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDB, userDB.getPassword(), getName());
        info.setCredentialsSalt(ByteSource.Util.bytes(userDB.getSalt()));
        return info;

    }

}複製程式碼

七:新增shiro配置

建立core→configurer→ShiroConfigurer

package com.example.demo.core.configurer;

import com.example.demo.core.shiro.CustomRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class ShiroConfigurer {

    /**
     * 注入自定義的realm,告訴shiro如何獲取使用者資訊來做登入或許可權控制
     */
    @Bean
    public Realm realm() {
        return new CustomRealm();
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        /**
         * setUsePrefix(false)用於解決一個奇怪的bug。在引入spring aop的情況下。
         * 在@Controller註解的類的方法中加入@RequiresRole註解,會導致該方法無法對映請求,導致返回404。
         * 加入這項配置能解決這個bug
         */
        creator.setUsePrefix(true);
        return creator;
    }

    /**
     * 這裡統一做鑑權,即判斷哪些請求路徑需要使用者登入,哪些請求路徑不需要使用者登入
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
        chain.addPathDefinition( "/userInfo/selectById", "authc, roles[admin]");
        chain.addPathDefinition( "/logout", "anon");
        chain.addPathDefinition( "/userInfo/selectAll", "anon");
        chain.addPathDefinition( "/userInfo/login", "anon");
        chain.addPathDefinition( "/**", "authc");
        return chain;
    }
}
複製程式碼

shiro提供和多個預設的過濾器,我們可以用這些過濾器來配置控制指定url的許可權:

配置縮寫對應的過濾器功能
anonAnonymousFilter指定url可以匿名訪問
authcFormAuthenticationFilter指定url需要form表單登入,預設會從請求中獲取usernamepassword,rememberMe等引數並嘗試登入,如果登入不了就會跳轉到loginUrl配置的路徑。我們也可以用這個過濾器做預設的登入邏輯,但是一般都是我們自己在控制器寫登入邏輯的,自己寫的話出錯返回的資訊都可以定製。
authcBasicBasicHttpAuthenticationFilter指定url需要basic登入
logoutLogoutFilter登出過濾器,配置指定url就可以實現退出功能,非常方便
noSessionCreationNoSessionCreationFilter禁止建立會話
permsPermissionsAuthorizationFilter需要指定許可權才能訪問
portPortFilter需要指定埠才能訪問
restHttpMethodPermissionFilter將http請求方法轉化成相應的動詞來構造一個許可權字串
rolesRolesAuthorizationFilter需要指定角色才能訪問
sslSslFilter需要https請求才能訪問
userUserFilter需要已登入或“記住我”的使用者才能訪問

八:在UserInfoController中新增登入方法

@PostMapping("/login")
public RetResult<UserInfo> login(String userName, String password) {
    Subject currentUser = SecurityUtils.getSubject();
    //登入
    try {
        currentUser.login(new UsernamePasswordToken(userName, password));
    }catch (IncorrectCredentialsException i){
        throw new ServiceException("密碼輸入錯誤");
    }
    //從session取出使用者資訊
    UserInfo user = (UserInfo) currentUser.getPrincipal();
    return RetResponse.makeOKRsp(user);
}複製程式碼

九:資料庫新增許可權資料

INSERT INTO `sys_role` VALUES ('1', '財務', '負責發工資', 'cw', '2018-05-26 00:37:52', null, '0');
INSERT INTO `sys_role` VALUES ('2', '人事', '負責員工', 'rs', '2018-05-26 00:38:18', null, '0');
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `sys_perm` VALUES ('1', '建立', '建立許可權', 'create', '2018-05-26 00:39:16', null, '0');
INSERT INTO `sys_perm` VALUES ('2', '刪除', '刪除許可權', 'delete', '2018-05-26 00:39:39', null, '0');
INSERT INTO `sys_perm` VALUES ('3', '修改', '修改許可權', 'update', '2018-05-26 00:39:58', null, '0');
INSERT INTO `sys_perm` VALUES ('4', '查詢', '查詢許可權', 'select', '2018-05-26 00:40:16', null, '0');
INSERT INTO `role_perm` VALUES ('1', '1', '1');
INSERT INTO `role_perm` VALUES ('2', '2', '1');
INSERT INTO `role_perm` VALUES ('3', '1', '2');
INSERT INTO `role_perm` VALUES ('4', '2', '2');
INSERT INTO `role_perm` VALUES ('5', '3', '2');
INSERT INTO `role_perm` VALUES ('6', '4', '2');複製程式碼

十:新增shiroUtilsController

package com.example.demo.controller;

import com.example.demo.core.ret.ServiceException;
import com.example.demo.model.UserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("shiroUtils")
public class ShiroUtilsController {

    @GetMapping("/noLogin")
    public void noLogin() {
        throw new UnauthenticatedException();
    }

    @GetMapping("/noAuthorize")
    public void noAuthorize() {
        throw new UnauthorizedException();
    }


    @PostMapping("/getNowUser")
    public UserInfo getNowUser() {
        UserInfo u = (UserInfo) SecurityUtils.getSubject().getPrincipal();
        return u;
    }

}複製程式碼

十一:新增錯誤異常碼

package com.example.demo.core.ret;

/**
 * @Description: 響應碼列舉,參考HTTP狀態碼的語義
 * @author 張瑤
 * @date 2018/4/19 09:42
 */
public enum RetCode {

   // 成功
   SUCCESS(200),

   // 失敗
   FAIL(400),

   // 未認證(簽名錯誤)
   UNAUTHORIZED(401),

   /** 未登入 */
   UNAUTHEN(4401),

   /** 未授權,拒絕訪問 */
   UNAUTHZ(4403),

   // 伺服器內部錯誤
   INTERNAL_SERVER_ERROR(500);

   public int code;

   RetCode(int code) {
      this.code = code;
   }
}複製程式碼

十二:新增異常攔截

@Override
	public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		exceptionResolvers.add(new HandlerExceptionResolver() {
			@Override
			public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
				RetResult<Object> result = new RetResult<Object>();
				// 業務失敗的異常,如“賬號或密碼錯誤”
				if (e instanceof ServiceException) {
					result.setCode(RetCode.FAIL).setMsg(e.getMessage()).setData(null);
					LOGGER.info(e.getMessage());
				} else if (e instanceof NoHandlerFoundException) {
					result.setCode(RetCode.NOT_FOUND).setMsg("介面 [" + request.getRequestURI() + "] 不存在");
				} else if (e instanceof UnauthorizedException) {
					result.setCode(RetCode.UNAUTHEN).setMsg("使用者沒有訪問許可權").setData(null);
				}else if (e instanceof UnauthenticatedException) {
					result.setCode(RetCode.UNAUTHEN).setMsg("使用者未登入").setData(null);
				}else if (e instanceof ServletException) {
					result.setCode(RetCode.FAIL).setMsg(e.getMessage());
				} else {
					result.setCode(RetCode.INTERNAL_SERVER_ERROR).setMsg("介面 [" + request.getRequestURI() + "] 內部錯誤,請聯絡管理員");
					String message;
					if (handler instanceof HandlerMethod) {
						HandlerMethod handlerMethod = (HandlerMethod) handler;
						message = String.format("介面 [%s] 出現異常,方法:%s.%s,異常摘要:%s", request.getRequestURI(), handlerMethod.getBean().getClass().getName(), handlerMethod.getMethod()
								.getName(), e.getMessage());
					} else {
						message = e.getMessage();
					}
					LOGGER.error(message, e);
				}
				responseResult(response, result);
				return new ModelAndView();
			}
		});
	}複製程式碼

十三:新增shiro路徑配置

application.properties中新增

#shiro配置
#使用者未登入
shiro.loginUrl=/shiroUtils/noLogin
#使用者沒有許可權
shiro.unauthorizedUrl=/shiroUtils/noAuthorize複製程式碼

十四:測試

輸入localhost:8080/userInfo/selectAll

我們可以看到可以拿到資料

從零搭建自己的SpringBoot後臺框架(十四)

輸入localhost:8080/userInfo/selectById

從零搭建自己的SpringBoot後臺框架(十四)

然後登陸

從零搭建自己的SpringBoot後臺框架(十四)

再次訪問localhost:8080/userInfo/selectById,我們可以看到shiro已經生效

從零搭建自己的SpringBoot後臺框架(十四)

修改使用者訪問許可權,重新啟動,重新登陸

chain.addPathDefinition( "/userInfo/selectById", "authc, roles[cw]");複製程式碼

再次訪問localhost:8080/userInfo/selectById

從零搭建自己的SpringBoot後臺框架(十四)

十五:優化

優化一:許可權不可能一直不變,當我們需要修改許可權時,我們需要手動修改shiro配置檔案,顯然是不合理的,最好的辦法是把許可權儲存在資料庫中,修改時修改資料庫

1:建立url資源表並新增資料

CREATE TABLE `sys_permission_init` (
  `id` varchar(255) NOT NULL,
  `url` varchar(255) DEFAULT NULL COMMENT '程式對應url地址',
  `permission_init` varchar(255) DEFAULT NULL COMMENT '對應shiro許可權',
  `sort` int(100) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `sys_permission_init` VALUES ('1', '/userInfo/login', 'anon', '1');
INSERT INTO `sys_permission_init` VALUES ('2', '/userInfo/selectAll', 'anon', '2');
INSERT INTO `sys_permission_init` VALUES ('3', '/logout', 'anon', '3');
INSERT INTO `sys_permission_init` VALUES ('4', '/**', 'authc', '0');
INSERT INTO `sys_permission_init` VALUES ('5', '/userInfo/selectAlla', 'authc, roles[admin]', '6');
INSERT INTO `sys_permission_init` VALUES ('6', '/sysPermissionInit/aaa', 'anon', '5');複製程式碼

這裡我們需要讓'/**'始終在最後,所以需要新增sort進行排序

2:根據工具生成mapper,dao,service,controller並新增我們需要的查詢方法

SysPermissionInitMapper.xml

<sql id="Base_Column_List">
  id, url, permission_init, sort
</sql>

<select id="selectAllOrderBySort" resultMap="BaseResultMap">
  SELECT
  <include refid="Base_Column_List"/>
  from sys_permission_init
  order by sort desc
</select>複製程式碼

SysPermissionInitMapper.java

List<SysPermissionInit> selectAllOrderBySort();複製程式碼

3:修改ShiroConfigurer.java

修改後如下

package com.example.demo.core.configurer;

import com.example.demo.core.shiro.CustomRealm;
import com.example.demo.model.SysPermissionInit;
import com.example.demo.service.SysPermissionInitService;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.List;

@Configuration
public class ShiroConfigurer {

    @Resource
    private SysPermissionInitService sysPermissionInitService;

    /**
     * 注入自定義的realm,告訴shiro如何獲取使用者資訊來做登入或許可權控制
     */
    @Bean
    public Realm realm() {
        return new CustomRealm();
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        /**
         * setUsePrefix(false)用於解決一個奇怪的bug。在引入spring aop的情況下。
         * 在@Controller註解的類的方法中加入@RequiresRole註解,會導致該方法無法對映請求,導致返回404。
         * 加入這項配置能解決這個bug
         */
        creator.setUsePrefix(true);
        return creator;
    }

    /**
     * 這裡統一做鑑權,即判斷哪些請求路徑需要使用者登入,哪些請求路徑不需要使用者登入
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
        List<SysPermissionInit> list = sysPermissionInitService.selectAllOrderBySort();
        for(int i = 0,length = list.size();i<length;i++){
            SysPermissionInit sysPermissionInit = list.get(i);
            chain.addPathDefinition(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit());
        }
        return chain;
    }
}複製程式碼

4:測試

輸入localhost:8080/userInfo/selectById

從零搭建自己的SpringBoot後臺框架(十四)

然後登陸

從零搭建自己的SpringBoot後臺框架(十四)

再次訪問localhost:8080/userInfo/selectById,我們可以看到shiro已經生效

從零搭建自己的SpringBoot後臺框架(十四)

修改資料庫使用者訪問許可權,重新啟動,重新登陸

UPDATE sys_permission_init
SET permission_init = 'authc, roles[cw]'
WHERE
	id = 5複製程式碼

再次訪問localhost:8080/userInfo/selectById

從零搭建自己的SpringBoot後臺框架(十四)

優化二:每次當我們修改許可權資訊時,都需要重啟伺服器,這顯然也是不合理的

1:建立shiroService

package com.example.demo.service;

import java.util.Map;

/**
 * shiro 動態更新許可權
 */
public interface ShiroService {

    Map<String, String> loadFilterChainDefinitions();

    /**
     * 動態修改許可權
     */
    void updatePermission();
}複製程式碼

2:建立shiroServiceImpl

package com.example.demo.service.impl;

import com.example.demo.model.SysPermissionInit;
import com.example.demo.service.ShiroService;
import com.example.demo.service.SysPermissionInitService;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ShiroServiceImpl implements ShiroService {

    @Autowired
    ShiroFilterFactoryBean shiroFilterFactoryBean;

    @Autowired
    SysPermissionInitService sysPermissionInitService;

    /**
     * 初始化許可權
     */
    @Override
    public Map<String, String> loadFilterChainDefinitions() {
        // 許可權控制map.從資料庫獲取
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        List<SysPermissionInit> list = sysPermissionInitService.selectAllOrderBySort();
        for (SysPermissionInit sysPermissionInit : list) {
            filterChainDefinitionMap.put(sysPermissionInit.getUrl(),
                    sysPermissionInit.getPermissionInit());
        }
        return filterChainDefinitionMap;
    }


    /**
     * 重新載入許可權
     */
    @Override
    public void updatePermission() {
        synchronized (shiroFilterFactoryBean) {
            AbstractShiroFilter shiroFilter = null;
            try {
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
                        .getObject();
            } catch (Exception e) {
                throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
            }
            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
                    .getFilterChainResolver();
            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
                    .getFilterChainManager();
            // 清空老的許可權控制
            manager.getFilterChains().clear();
            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
            shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions());
            // 重新構建生成
            Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue().trim().replace(" ", "");
                manager.createChain(url, chainDefinition);
            }
            System.out.println("更新許可權成功!!");
        }
    }
}複製程式碼

3:修改shiroUtilsController

新增如下程式碼

/**
 * @Description: 重新載入shiro許可權
 * @throws Exception
 */
@PostMapping("/updatePermission")
public void updatePermission() throws Exception {
    shiroService.updatePermission();
}複製程式碼

4:測試

輸入localhost:8080/userInfo/selectById

從零搭建自己的SpringBoot後臺框架(十四)

修改許可權

UPDATE sys_permission_init
SET permission_init = 'authc, roles[cw1]'
WHERE
id = 5複製程式碼

輸入localhost:8080/shiroUtils/updatePermission  重新載入許可權

注意:此刻我們並沒有修改程式任何一個地方,也沒有重啟伺服器

輸入localhost:8080/userInfo/selectById

從零搭建自己的SpringBoot後臺框架(十四)

成功!


補充:生成加密之後的使用者密碼

檔案在core→utils→test.java

public static void main(String []ages ){
    //加密方式
    String hashAlgorithmName = "md5";
    //原密碼
    String credentials = "123456";
    //加密次數
    int hashIterations = 1024;
    //加密鹽值,大家可以用生成字串的方法
    String hash = "wxKYXuTPST5SG0jMQzVPsg==";
    ByteSource credentialsSalt = ByteSource.Util.bytes(hash);
    String password = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations).toHex();
    System.out.println(password);
}複製程式碼


專案地址

碼雲地址: gitee.com/beany/mySpr…

GitHub地址: github.com/MyBeany/myS…

寫文章不易,如對您有幫助,請幫忙點下star從零搭建自己的SpringBoot後臺框架(十四)

結尾

新增shiro許可權保護介面功能已完成,後續功能接下來陸續更新,有問題可以聯絡我mr_beany@163.com。另求各路大神指點,感謝大家。


相關文章