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)用中某些資源或操作的訪問(wèn)權(quán)限。在配置文件中,你可以設(shè)定哪些角色可以訪問(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");
//訪問(wèn)需要認(rèn)證的頁(yè)面如果未登錄會(huì)跳轉(zhuǎn)到/login
factoryBean.setLoginUrl("/login");
//訪問(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è)試看一下就行了
//訪問(wèn)需要認(rèn)證的頁(yè)面如果未登錄會(huì)跳轉(zhuǎn)到/login路由進(jìn)行登陸
factoryBean.setLoginUrl("/unLogin");用戶(hù)訪問(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)文章
MyBatis-Plus的物理刪除和邏輯刪除(使用場(chǎng)景)
數(shù)據(jù)庫(kù)中的數(shù)據(jù)刪除會(huì)分為兩種:物理刪除 和 邏輯刪除,接下來(lái)通過(guò)本文給大家介紹MyBatis-Plus的物理刪除和邏輯刪除使用場(chǎng)景分析,感興趣的朋友一起看看吧2021-09-09
Java自定義類(lèi)加載器實(shí)現(xiàn)類(lèi)隔離詳解
由于每種組件的不同版本所依賴(lài)的jar包不同,我們可以借鑒tomcat的實(shí)現(xiàn)方式,通過(guò)自定義類(lèi)加載器打破雙親委派機(jī)制來(lái)實(shí)現(xiàn)類(lèi)隔離,從而達(dá)到操作多組件多版本的目的。本文就來(lái)和大家詳細(xì)聊聊實(shí)現(xiàn)方法2023-03-03
基于RecyclerChart的KLine繪制Volume實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了基于RecyclerChart的KLine繪制Volume實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
SpringCloud超詳細(xì)講解微服務(wù)網(wǎng)關(guān)Zuul基礎(chǔ)
這篇文章主要介紹了SpringCloud?Zuul微服務(wù)網(wǎng)關(guān),負(fù)載均衡,熔斷和限流,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10
java中獲取當(dāng)前服務(wù)器的Ip地址的方法
本篇文章主要介紹了java中獲取當(dāng)前服務(wù)器的Ip地址的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02

