這篇文章運用簡單易懂的例子給大家介紹使用Springboot 整合shiro實現權限控制的示例,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
一、開發環境:
1、mysql - 5.7
2、navicat(mysql客戶端管理工具)
3、idea 2017.2
4、jdk8
5、tomcat 8.5
6、springboot2.1.3
7、mybatis 3
8、shiro1.4
9、maven3.3.9
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CB46ByC1-1604249108144)(img/shiro01.png)]
添加 web、lombok、thymeleaf、jdbc、mysql、mybatis等模塊;
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <!--配置shiro依賴包--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!--配置數據庫連接池依賴--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.26</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> <!--配置lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NNuns5Pt-1604249108145)(img/shiro02.png)]
#配置服務端口號 server: port: 8080 #配置數據源 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/my_shiro?useUnicode=true&characterEncoding=utf-8 username: root password: 59852369 #配置mybatis mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.qf.domain.pojo
​ SysUser.java
package com.qf.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* Created by 54110 on 2019-07-05.
*/
@Data
public class SysUser implements Serializable {
private int userId;//用戶id
private String loginName;//登錄名
private String password;//
private Integer state;
private Date createTime;
private String realname;
}SysPermission.java
package com.qf.domain;
import lombok.Data;
import java.io.Serializable;
/**
* Created by 54110 on 2019-07-05.
*/
@Data
public class SysPermission implements Serializable {
private int permId;
private String permName;//權限名稱
private String permUrl;//權限操作地址(路徑)
private String menuName;//菜單名
private String menuLevel;//菜單級別(11:一級;12:二級。。。)
private String menuCode;//菜單編碼(每級兩位數字)
private int ifValid;
private String parentCode;
}SysUserMapper.java
package com.qf.mapper;
import com.qf.domain.SysUser;
import org.apache.ibatis.annotations.Mapper;
/**
* Created by 54110 on 2019-07-05.
*/
@Mapper
public interface SysUserMapper {
public SysUser findUserByUsername(String username);
}SysPermissionMapper
package com.qf.mapper;
import com.qf.domain.SysPermission;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* Created by 54110 on 2019-07-05.
*/
@Mapper
public interface SysPermissionMapper {
// 根據用戶登錄名查詢其所擁有的權限
public List<SysPermission> findPermissionsByLoginName(String loginName);
}SysUsersMapper.xml
<?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.qf.mapper.SysUserMapper">
<resultMap type="com.qf.domain.SysUser" id="userMap">
<id column="USERID" property="userid" />
<result column="LOGIN_NAME" property="loginName" />
<result column="PASSWORD" property="password" />
<result column="STATE" property="state" />
<result column="CREATE_TIME" property="createTime" />
<result column="REALNAME" property="realname" />
</resultMap>
<sql id="tbusers_columns">
PASSWORD,LOGIN_NAME,CREATE_TIME,REALNAME,STATE
</sql>
<!--根據用戶名查詢對象 -->
<select id="findUserByUsername" parameterType="string" resultMap="userMap">
SELECT
<include refid="tbusers_columns"></include>
FROM
TB_SYS_USER US
WHERE
US.LOGIN_NAME = #{name}
</select>
</mapper>SysPermissionMapper.xml
<?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.qf.mapper.SysPermissionMapper">
<resultMap type="com.qf.domain.SysPermission" id="permMap">
<id column="PERMISSION_ID" property="permId" />
<result column="PER_NAME" property="permName" />
<result column="MENU_URL" property="permUrl" />
<result column="MENU_NAME" property="menuName" />
<result column="MENU_TYPE" property="menuLevel" />
<result column="MENU_CODE" property="menuCode" />
<result column="PARENT_CODE" property="parentCode" />
<result column="IF_ViLID" property="ifValid" />
</resultMap>
<select id="findPermissionsByLoginName" parameterType="string" resultMap="permMap">
SELECT
p.*
FROM
TB_SYS_USER us ,
TB_USER_ROLE ur,
TB_SYS_ROLE r,
TB_ROLE_PERMISSION rp,
TB_SYS_PERMISSION p
WHERE
us.USERID = ur.USER_ID AND ur.ROLE_ID = r.ROLE_ID
AND r.ROLE_ID = rp.ROLE_ID AND rp.PERMISSION_ID = p.PERMISSION_ID
AND trim(us.LOGIN_NAME) = #{loginName}
ORDER BY p.MENU_CODE
</select>
</mapper>SysUsersServiceImpl.java
package com.qf.service.impl;
import com.qf.domain.SysUser;
import com.qf.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by 54110 on 2019-07-05.
*/
@Service
public class SysUsersServiceImpl {
@Autowired
private SysUserMapper userMapper;
public SysUser queryUserByLoginName(String loginName) {
SysUser tbUsers = userMapper.findUserByUsername(loginName);
return tbUsers;
}
}​SysPermissionServiceImpl.java
package com.qf.service.impl;
import com.qf.domain.SysPermission;
import com.qf.mapper.SysPermissionMapper;
import com.qf.service.SysPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by 54110 on 2019-07-05.
*/
@Service
public class SysPermissionServiceImpl implements SysPermissionService {
@Autowired
private SysPermissionMapper permMapper;
@Override
public List<SysPermission> queryPermissionsByLoginName(String loginName) {
List<SysPermission> list = permMapper.findPermissionsByLoginName(loginName);
return list;
}
}UserController.java
package com.qf.controller;
import com.qf.service.SysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
/**
* Created by 54110 on 2019-07-05.
*/
@Controller
public class UserController {
@Autowired
private SysUserService userService;
// 登錄頁(view)展示
@RequestMapping("/login")
public String showlogin(){
return "login";
}
/**
* 登錄處理
* @param map 用戶登錄表單數據
* @return 邏輯視圖
*/
@RequestMapping(value="dealLogin" ,method= RequestMethod.POST)
public String dealLogin(@RequestParam Map<String,Object> map){
System.out.println( map.values().toString());
try {
Subject subject = SecurityUtils.getSubject();//從安全管理器中獲取主體對象
UsernamePasswordToken token = new UsernamePasswordToken();//構建令牌對象
token.setUsername(map.get("name").toString());//賦身份信息
token.setPassword(map.get("password").toString().toCharArray());//賦憑證信息
subject.login(token);//使用主體的login方法判定用戶的權限
if(subject.isAuthenticated()){
// 已登陸
// 用戶信息及權限信息的存儲(session|| redis)
return "main";
}
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登錄失敗");
}
return "login";
}
// 登錄且擁有user:
@RequestMapping("/one")
public String showCaseOne(){
return "one";
}
@RequestMapping("/two")
public String showCaseTwo(){
return "two";
}
// 權限不足時,響應的頁面
@RequestMapping("/unauth")
public String showPermission(){
return "unauth";
}
// 用戶注銷操作
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();//登出
return "redirect:login";
}
}a、自定義安全策略
MyShiroRealm.java
package com.qf.shiro;
import com.qf.domain.SysPermission;
import com.qf.domain.SysUser;
import com.qf.service.SysPermissionService;
import com.qf.service.SysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* Created by 54110 on 2019-07-05.
*/
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserServiceImpl;
@Autowired
private SysPermissionService sysPermissionServiceImpl;
private String username;
// 系統授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Subject subject = SecurityUtils.getSubject();//獲取主體對象
String username =(String ) subject.getPrincipal();//獲取用戶身份信息
List<SysPermission> permissions = sysPermissionService.queryPermissionsByLoginName(username);//根據用戶名獲取用戶的權限信息
// 權限去重
Collection<String > perms = new HashSet<>();
for (SysPermission perm: permissions ) {
perms.add(perm.getPermName());
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(perms);//授權
return simpleAuthorizationInfo;
}
//用戶認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();//獲取用戶信息
//根據用戶信息查詢數據庫獲取后端的用戶身份,轉交給securityManager判定
SysUser user1 = sysUserService.queryUserByLoginName(username);//從數據庫直接取
System.out.println(user1);
if(user1!=null) {
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user1.getLoginName(), user1.getPassword(), getName());
return simpleAuthenticationInfo;
}
return null;
}
}b、自定義Shiro配置管理
ShiroConfig.java
package com.qf.config;
import com.qf.shiro.MyShiroRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* Created by 54110 on 2019-07-05.
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String ,String> map = new HashMap<>();
map.put("/main","authc"); //必須登錄才可訪問
map.put("/one","perms[user_edit]");//只有特定權限(“user_edit”)的用戶登錄后才可訪問
map.put("/two","perms[user_forbidden]");//只有特定權限(“user_forbidden”)的用戶登錄后才可訪問
shiroFilterFactoryBean.setLoginUrl("/login");//設置登錄頁(匿名)
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");//權限不足的錯誤提示頁
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);//裝配攔截策略
return shiroFilterFactoryBean;
}
// 配置安全管理器(注入Realm對象)
@Bean(name="defaultWebSecurityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(myShiroRealm);
return defaultWebSecurityManager;
}
@Bean(name="myShiroRealm") //使用該注解是的Realm對象由spring容器管理
public MyShiroRealm myShiroRealm(){
MyShiroRealm shiroRealm = new MyShiroRealm();
return shiroRealm;
}
}​ 地址欄輸入http://localhost:8080/login
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BdyHhsoT-1604249108147)(img\shiro03.png)]
​ 使用用戶:admin密碼:admin登錄,登錄成功后顯示頁面如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LdoaSOGM-1604249108149)(img/shiro04.png)]
​ 因為admin2用戶擁有case one功能的操作權限,所以當鼠標單擊case one鏈接時,顯示如下成功訪問頁面
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Co1rUWJi-1604249108150)(img/shiro05.png)]
因為admin2沒有case two訪問權限,當用戶單擊case two時,會顯示無權限訪問的頁面:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-geha4PCi-1604249108150)(img/shiro06.png)]
當單擊logout鏈接時,系統重回登錄頁。此時使用用戶test2密碼test2再次登錄。因test2用戶無case one權限,有case two權限,所以當test2用戶單擊case two時會顯示如下頁面:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AWZrOeGj-1604249108151)(img/shiro07.png)]
//只有特定權限(“user_edit”)的用戶登錄后才可訪問
// map.put("/one","perms[user_edit]");
//只有特定權限(“user_forbidden”)的用戶登錄后才可訪問
// map.put("/two","perms[user_forbidden]");/**
* 開啟Shiro注解(如@RequiresRoles,@RequiresPermissions),
* 需借助SpringAOP掃描使用Shiro注解的類,并在必要時進行安全邏輯驗證
* 配置以下兩個bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 開啟aop注解支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
return authorizationAttributeSourceAdvisor;
}// 登錄且擁有user:
@RequiresPermissions(value={"user_edit"})
@RequestMapping("/one")
public String showCaseOne(){
return "one";
}
@RequiresPermissions(value={"user_forbidden"})
@RequestMapping("/two")
public String showCaseTwo(){
return "two";
}此事有權限訪問的也頁面正常,但未授權的頁面,無法進入提示頁,顯示如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pM9lRu2Z-1604249108152)(img/shiro08.png)]
​ 后臺亦拋出org.apache.shiro.authz.AuthorizationException異常:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-k8KTUuoi-1604249108152)(img/shiro09.png)]
package com.jeffrey.exception;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
/**
* Created by jeffrey on 2019/4/8.
*/
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = UnauthorizedException.class)//處理訪問方法時權限不足問題
public String defaultErrorHandler(HttpServletRequest req, Exception e) {
return "unauth";
}
}在數據表中存的密碼不應該是12345,而應該是12345加密之后的字符串,而且還要求這個加密算法是不可逆的,即由加密后的字符串不能反推回來原來的密碼,如果能反推回來那這個加密是沒有意義的。
著名的加密算法,比如 MD5,SHA1
1). 如何把一個字符串加密為MD5
2). 使用MD5加密算法后,前臺用戶輸入的字符串如何使用MD5加密,需要做的是將當前的Realm 的credentialsMatcher屬性,替換為Md5CredentialsMatcher 由于Md5CredentialsMatcher已經過期了,推薦使用HashedCredentialsMatcher 并設置加密算法即可。
1). 修改ShiroConfig.java文件添加如下內容;
/**
* 密碼校驗規則HashedCredentialsMatcher
* 這個類是為了對密碼進行編碼的 ,
* 防止密碼在數據庫里明碼保存 , 當然在登陸認證的時候 ,
* 這個類也負責對form里輸入的密碼進行編碼
* 處理認證匹配處理器:如果自定義需要實現繼承HashedCredentialsMatcher
*/
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式為MD5
credentialsMatcher.setHashAlgorithmName("MD5");
//加密次數
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean("myShiroRealm")
public MyShiroRealm myShiroRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
MyShiroRealm authRealm = new MyShiroRealm();
authRealm.setAuthorizationCachingEnabled(false);
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}​ 2).修改MyRealm.java的認證邏輯如下:
//用戶認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();//獲取用戶信息
SysUser user1 = sysUserService.queryUserByLoginName(username);//從數據庫直接取
System.out.println(user1);
if(user1!=null) {
//當前realm對象的name
String realmName = getName();
//鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
//封裝用戶信息,構建AuthenticationInfo對象并返回
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(username, user1.getPassword(), credentialsSalt, realmName);
return authcInfo;
}
return null;
}​ 3). 通過 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 我們可以得到"12345"經過MD5 加密1024后的字符串;
package com.jeffrey;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* Created by jeffrey on 2019/4/8.
*/
public class MD5Salt {
public static void main(String[] args){
String hashAlgorithName = "MD5";//加密算法
String password = "12345";//登陸時的密碼
int hashIterations =1024;//加密次數
ByteSource credentialsSalt = ByteSource.Util.bytes("admin2");//使用登錄名做為salt
SimpleHash simpleHash = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);
System.out.println("ok "+simpleHash);
}
}​ 使用密文替換數據庫中的明文密碼;
為什么使用 MD5 鹽值加密:
希望即使兩個原始密碼相同,加密得到的兩個字符串也不同。
如何做到:
1). 在 doGetAuthenticationInfo 方法返回值創建 SimpleAuthenticationInfo 對象的時候, 需要使用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 構造器
2). 使用 ByteSource.Util.bytes() 來計算鹽值.
3). 鹽值需要唯一: 一般使用隨機字符串或 user id
4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 來計算鹽值加密后的密碼的值.
關于使用Springboot 整合shiro實現權限控制的示例就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。