Shiro與Springboot整合開(kāi)發(fā)的基本步驟過(guò)程詳解
前言
在 Spring Boot 中做權(quán)限管理,一般來(lái)說(shuō),主流的方案是 Spring Security ,但是,僅僅從技術(shù)角度來(lái)說(shuō),也可以使用 Shiro。
一、基本開(kāi)發(fā)步驟
在整合之前,讓我們先來(lái)了解一下Shiro開(kāi)發(fā)的基本步驟:
1. 導(dǎo)入Shiro依賴(lài):
首先,在你的項(xiàng)目中添加Shiro的依賴(lài),你可以通過(guò)Maven或者Gradle來(lái)管理項(xiàng)目依賴(lài),確保你的項(xiàng)目中已經(jīng)引入Shiro的相關(guān)庫(kù)文件。
2. 配置Shiro:
接下來(lái),你需要配置Shiro的一些核心組件,如安全管理器、認(rèn)證器、授權(quán)器等。你可以在配置文件中添加相關(guān)的配置,或者在代碼中進(jìn)行編程式配置。
3. 編寫(xiě)和配置Realm:
Realm是Shiro進(jìn)行認(rèn)證和授權(quán)的核心組件之一。你需要編寫(xiě)一個(gè)實(shí)現(xiàn)了Shiro提供的Realm接口的類(lèi),并根據(jù)你的業(yè)務(wù)需求,實(shí)現(xiàn)其中的認(rèn)證和授權(quán)邏輯。在Shiro的配置中,將你的Realm配置為Shiro的認(rèn)證和授權(quán)來(lái)源。
4. 設(shè)計(jì)登錄和認(rèn)證流程:
登錄是Shiro應(yīng)用中很重要的一部分。你可以設(shè)計(jì)一個(gè)登錄界面,接受用戶(hù)輸入的用戶(hù)名和密碼,并將其傳遞給Shiro進(jìn)行認(rèn)證。在認(rèn)證過(guò)程中,Shiro會(huì)調(diào)用你實(shí)現(xiàn)的Realm來(lái)驗(yàn)證用戶(hù)的身份信息。
5. 設(shè)計(jì)和配置授權(quán)規(guī)則:
授權(quán)是Shiro應(yīng)用中的另一個(gè)重要方面。你可以在Realm中定義角色和權(quán)限,并根據(jù)用戶(hù)的身份和角色,來(lái)控制用戶(hù)對(duì)應(yīng)用中某些資源或操作的訪(fǎng)問(wèn)權(quán)限。在配置文件中,你可以設(shè)定哪些角色可以訪(fǎng)問(wèn)哪些資源。
6. 集成Shiro到你的應(yīng)用:
你需要將Shiro集成到你的應(yīng)用中,以便能夠?qū)τ脩?hù)進(jìn)行認(rèn)證和授權(quán)??梢酝ㄟ^(guò)編寫(xiě)過(guò)濾器、注解或者攔截器的方式,將Shiro應(yīng)用到你的業(yè)務(wù)代碼中。
7. 測(cè)試和調(diào)試:
在完成上述步驟后,你可以對(duì)應(yīng)用進(jìn)行測(cè)試和調(diào)試,確保Shiro在你的應(yīng)用中正確地進(jìn)行認(rèn)證和授權(quán)。
8. 安全加固:
最后,你可以進(jìn)行一些額外的安全加固措施,如密碼加密存儲(chǔ)、登錄日志記錄等,以提高應(yīng)用的安全性。
二、Springboot整合開(kāi)發(fā)
1. 數(shù)據(jù)庫(kù)設(shè)計(jì)
可以隨意建設(shè)一個(gè)關(guān)系型數(shù)據(jù)庫(kù)的多表,完成相應(yīng)的一個(gè)屬性值與關(guān)系映射。權(quán)限設(shè)計(jì)通常采用RBAC即用戶(hù)、角色、權(quán)限、用戶(hù)-角色、角色-權(quán)限5張表。
2.前期準(zhǔn)備
導(dǎo)入jar包,準(zhǔn)備相應(yīng)pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency>
業(yè)務(wù)實(shí)現(xiàn)
可以通過(guò)通過(guò)Mybaits-plus或mybatis、Hibernate 查詢(xún)用戶(hù)的信息、用戶(hù)角色信息、用戶(hù)的權(quán)限信息。
@Service @Transactional(rollbackFor=Exception.class) public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; //查詢(xún)用戶(hù)信息 @Override public User getUserByUserId(String userId) { Assert.notNull(userId, "userId不能為空"); User user= userMapper.getUserByUserId(userId); if(user==null) { throw new UnknownAccountException("用戶(hù)名或密碼不正確"); } // if("0".equals(user.getActive())) { throw new UnknownAccountException("用戶(hù)狀態(tài)不正確"); } return user; } //獲取用戶(hù)角色 @Override public List<Role> getRolesByUserOid(Integer userOid) { Assert.notNull(userOid, "userOid不能為空"); return userMapper.getRolesByUserOid(userOid); } //獲取用戶(hù)權(quán)限 @Override public List<Func> getResByRoleOid(Collection<Integer> roleOids) { Assert.notNull(roleOids, "roleOid不能為空"); return userMapper.getResByRoleOid(roleOids); } }
Dao層
public interface UserMapper extends BaseMapper<User> { User getUserByUserId(String userId); List<Role> getRolesByUserOid(@Param("userOid")Integer userOid); List<Func> getResByRoleOid(@Param("roleOids")Collection<Integer> roleOids); }
配置文件UserMapper.xml,并掃描對(duì)于該文件進(jìn)行查找映射
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.skywares.fw.security.mapper.UserMapper"> <select id="getUserByUserId" resultType="com.skywares.fw.security.pojo.User"> select oid, userId, userName, active, gender, mobile, email, pwd from fw_security_user u where u.userId=#{userId} </select> <select id="getRolesByUserOid" resultType="com.skywares.fw.security.pojo.Role"> select r.oid, r.roleId, r.active, r.updateUser, r.updateDate from fw_security_user u join fw_security_user_role ur on ur.userOid = u.oid join fw_security_role r on r.oid = ur.roleOid where u.active='1' and u.oid =#{userOid} </select> <select id="getResByRoleOid" resultType="com.skywares.fw.security.pojo.Func"> select res.oid, res.resId, res.defaultLabel, res.seq, res.parentoid, res.url, res.exturl, res.type, res.active from fw_security_res res join fw_security_role_res r on r.resOid = res.oid join fw_security_role role on role.oid = r.roleOid where role.oid in <foreach collection="roleOids" item="oid" open="(" close=")" separator=","> #{oid} </foreach> order BY res.oid desc </select> </mapper>
三、Shiro的集成
自定義Realm實(shí)現(xiàn)認(rèn)證
public class CustomerRealm extends AuthorizingRealm { @Autowired private UserService userService; //授權(quán)認(rèn)證 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); Integer userOid=user.getOid(); List<Role> roleList= userService.getRolesByUserOid(userOid); //用戶(hù)角色 Set<String> roleSet=new HashSet<>(); //權(quán)限信息 Set<String> funcSet=new HashSet<>(); Set<Integer> roleOids=new HashSet<>(); //查詢(xún)角色 if(roleList!=null && !roleList.isEmpty()) { roleList.stream().forEach(t->{ roleSet.add(String.valueOf(t.getRoleId())); roleOids.add(t.getOid()); }); } //查詢(xún)權(quán)限 List<Func> funcList= userService.getResByRoleOid(roleOids); if(funcList!=null && !funcList.isEmpty()){ for(Func func:funcList) { funcSet.add(func.getUrl()); } } //添加角色 info.addRoles(roleSet); //添加權(quán)限 info.addStringPermissions(funcSet); return info; } //用戶(hù)認(rèn)證 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authToken) throws AuthenticationException { //采用用戶(hù)名和密碼方式 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authToken; String userId = usernamePasswordToken.getUsername(); //密碼 String password = new String(usernamePasswordToken.getPassword()); // 通過(guò)用戶(hù)id獲取用戶(hù)信息 User user = userService.getUserByUserId(userId); //認(rèn)證。密碼進(jìn)行加密處理 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(),new CustByteSource(user.getUserId()),getName()); return info; } }
其中 doGetAuthenticationInfo:實(shí)現(xiàn)用戶(hù)的認(rèn)證,本文采用的是用戶(hù)名和密碼的方式。 doGetAuthorizationInfo :加載用戶(hù)的授權(quán)信息 CustByteSource: 用戶(hù)自定義的加密方式
可以自定義加密方式
public class CustByteSource implements ByteSource, Serializable { private static final long serialVersionUID = -3818806283942882146L; private byte[] bytes; private String cachedHex; private String cachedBase64; public CustByteSource() { } public CustByteSource(byte[] bytes) { this.bytes = bytes; } public CustByteSource(char[] chars) { this.bytes = CodecSupport.toBytes(chars); } public CustByteSource(String string) { this.bytes = CodecSupport.toBytes(string); } public CustByteSource(ByteSource source) { this.bytes = source.getBytes(); } public CustByteSource(File file) { this.bytes = new CustByteSource.BytesHelper().getBytes(file); } public CustByteSource(InputStream stream) { this.bytes = new CustByteSource.BytesHelper().getBytes(stream); } public static boolean isCompatible(Object o) { return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream; } @Override public byte[] getBytes() { return this.bytes; } @Override public boolean isEmpty() { return this.bytes == null || this.bytes.length == 0; } @Override public String toHex() { if (this.cachedHex == null) { this.cachedHex = Hex.encodeToString(getBytes()); } return this.cachedHex; } @Override public String toBase64() { if (this.cachedBase64 == null) { this.cachedBase64 = Base64.encodeToString(getBytes()); } return this.cachedBase64; } @Override public String toString() { return toBase64(); } @Override public int hashCode() { if (this.bytes == null || this.bytes.length == 0) { return 0; } return Arrays.hashCode(this.bytes); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof ByteSource) { ByteSource bs = (ByteSource) o; return Arrays.equals(getBytes(), bs.getBytes()); } return false; } private static final class BytesHelper extends CodecSupport { /** * 嵌套類(lèi)也需要提供無(wú)參構(gòu)造器 */ private BytesHelper() { } public byte[] getBytes(File file) { return toBytes(file); } public byte[] getBytes(InputStream stream) { return toBytes(stream); } } }
通過(guò)shiro提供加密方式針對(duì)密碼進(jìn)行加密處理,用戶(hù)注冊(cè)獲取密碼方式如下:
public static final String md5Pwd(String salt,String pwd) { //加密方式 String hashAlgorithmName = "MD5"; //鹽:為了即使相同的密碼不同的鹽加密后的結(jié)果也不同 ByteSource byteSalt = ByteSource.Util.bytes(salt); //加密次數(shù) int hashIterations = 2; SimpleHash result = new SimpleHash(hashAlgorithmName, pwd, byteSalt, hashIterations); return result.toString(); }
Shiro核心配置
@Configuration public class ShiroConfig { // 自定義密碼加密規(guī)則 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher =new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); hashedCredentialsMatcher.setHashIterations(2); //true 代表Hex編碼,fasle代表采用base64編碼 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; } // 自定義認(rèn)證 @Bean public CustomerRealm customerRealm() { CustomerRealm customerRealm=new CustomerRealm(); customerRealm.setCredentialsMatcher(hashedCredentialsMatcher()); customerRealm.setCachingEnabled(false); return customerRealm; } //需要定義DefaultWebSecurityManager,否則會(huì)報(bào)bean沖突 @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customerRealm()); securityManager.setRememberMeManager(null); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //給filter設(shè)置安全管理 factoryBean.setSecurityManager(securityManager); //配置系統(tǒng)的受限資源 Map<String,String> map = new HashMap<>(); //登錄請(qǐng)求無(wú)需認(rèn)證 map.put("/login", "anon"); //其他請(qǐng)求需要認(rèn)證 map.put("/**", "authc"); //訪(fǎng)問(wèn)需要認(rèn)證的頁(yè)面如果未登錄會(huì)跳轉(zhuǎn)到/login factoryBean.setLoginUrl("/login"); //訪(fǎng)問(wèn)未授權(quán)頁(yè)面會(huì)自動(dòng)跳轉(zhuǎn)到/unAuth factoryBean.setUnauthorizedUrl("/unAuth"); factoryBean.setFilterChainDefinitionMap(map); return factoryBean; } /** * 開(kāi)啟注解方式,頁(yè)面可以使用注解 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
四、測(cè)試
// 登錄測(cè)試 @Controller @RequestMapping("") public class LoginController { @RequestMapping("/login") @ResponseBody public String login(@RequestParam String userName,@RequestParam String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken =new UsernamePasswordToken(userName, password); subject.login(usernamePasswordToken); return "成功"; } /** * 用戶(hù)未登錄 * @return */ @RequestMapping("/unLogin") public String unLogin() { return "login.html"; } /** * 用戶(hù)未授權(quán) * @return */ @RequestMapping("/unAuth") public String unAuth() { return "unAuth.html"; } } // 角色和權(quán)限測(cè)試 @RestController @RequestMapping("/app/sys/user") public class UserController { @RequestMapping("/list") @RequiresPermissions("/app/sys/user/list") public String list() { return "成功"; } @RequestMapping("/roleTest") @RequiresRoles("admin1") public String roleTest() { return "成功"; } @RequestMapping("/resourceTest") @RequiresPermissions("/app/sys/user/list1") public String resourceTest() { return "成功"; } }
這里就做一個(gè)授權(quán)測(cè)試看一下就行了
//訪(fǎng)問(wèn)需要認(rèn)證的頁(yè)面如果未登錄會(huì)跳轉(zhuǎn)到/login路由進(jìn)行登陸 factoryBean.setLoginUrl("/unLogin");
用戶(hù)訪(fǎng)問(wèn)/login請(qǐng)求輸入正確的用戶(hù)名和密碼
到此這篇關(guān)于Shiro與Springboot整合開(kāi)發(fā)的文章就介紹到這了,更多相關(guān)Shiro與Springboot整合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java開(kāi)發(fā)常用類(lèi)庫(kù)之Hutool詳解
這篇文章主要介紹了Java開(kāi)發(fā)常用類(lèi)庫(kù)之Hutool,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02Java 并發(fā)編程的可見(jiàn)性、有序性和原子性
這篇文章主要介紹了Java 并發(fā)編程的可見(jiàn)性、有序性和原子性的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java并發(fā)編程,感興趣的朋友可以了解下。2020-11-11作為程序員必須掌握的Java虛擬機(jī)中的22個(gè)重難點(diǎn)(推薦0
這篇文章主要介紹了Java虛擬機(jī)中22個(gè)重難點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03實(shí)例詳解MyBatis-plus自動(dòng)填充功能
每次對(duì)數(shù)據(jù)進(jìn)行新增、刪除、修改時(shí)都需要對(duì)這些字段進(jìn)行設(shè)置,雖然新增時(shí)間和修改時(shí)間可以使用數(shù)據(jù)庫(kù)的時(shí)間,但是新增人和修改人就不能使用這樣的功能,下面小編給大家介紹下MyBatis-plus自動(dòng)填充功能的實(shí)例代碼,感興趣的朋友一起看看吧2022-01-01ChatGPT-4.0未來(lái)已來(lái) 你來(lái)不來(lái)
最近聽(tīng)說(shuō)了一個(gè)非?;鸬募夹g(shù)ChatGPT4.0,今天這篇文章就給大家介紹一下ChatGPT究竟是什么東東,不得不說(shuō)ChatGPT是真的強(qiáng),下面就讓我們一起了解究竟什么是ChatGPT吧2023-03-03Spring條件注解@ConditionnalOnClass的原理分析
這篇文章主要介紹了Spring條件注解@ConditionnalOnClass的原理分析,所謂@ConditionalOnClass注解,翻譯過(guò)來(lái)就是基于class的條件,它為所標(biāo)注的類(lèi)或方法添加限制條件,當(dāng)該條件的值為true時(shí),其所標(biāo)注的類(lèi)或方法才能生效,需要的朋友可以參考下2023-12-12Springboot如何使用logback實(shí)現(xiàn)多環(huán)境配置?
上一篇文章中老顧介紹了logback基本配置,了解了日志配置的基本方式.我們平時(shí)在系統(tǒng)開(kāi)發(fā)時(shí),開(kāi)發(fā)環(huán)境與生產(chǎn)環(huán)境的日志配置會(huì)不一樣;那今天老顧就跟大家介紹一下如何實(shí)現(xiàn)多環(huán)境配置,需要的朋友可以參考下2021-06-06Java?Web?Axios實(shí)現(xiàn)前后端數(shù)據(jù)異步交互實(shí)例代碼
Axios作為一個(gè)流行的前端?HTTP?通信庫(kù),可以極大地簡(jiǎn)化前端與后端之間的數(shù)據(jù)交互,這篇文章主要介紹了Java?Web?Axios實(shí)現(xiàn)前后端數(shù)據(jù)異步交互的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09