者:社會(huì)主義接班人cnblogs.com/5ishare/p/10461073.html
用戶權(quán)限管理一般是對(duì)用戶頁面、按鈕的訪問權(quán)限管理。Shiro框架是一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼和會(huì)話管理,對(duì)于Shiro的介紹這里就不多說。本篇博客主要是了解Shiro的基礎(chǔ)使用方法,在權(quán)限管理系統(tǒng)中集成Shiro實(shí)現(xiàn)登錄、url和頁面按鈕的訪問控制。
一、引入依賴
使用SpringBoot集成Shiro時(shí),在pom.xml中可以引入shiro-spring-boot-web-starter。由于使用的是thymeleaf框架,thymeleaf與Shiro結(jié)合需要 引入thymeleaf-extras-shiro。
二、增加Shiro配置
有哪些url是需要攔截的,哪些是不需要攔截的,登錄頁面、登錄成功頁面的url、自定義的Realm等這些信息需要設(shè)置到Shiro中,所以創(chuàng)建Configuration文件ShiroConfig。
ShiroDialect這個(gè)bean對(duì)象是在thymeleaf與Shiro結(jié)合,前端html訪問Shiro時(shí)使用。
三、自定義Realm
在自定義的Realm中繼承了AuthorizingRealm抽象類,重寫了兩個(gè)方法:doGetAuthorizationInfo和doGetAuthenticationInfo。doGetAuthorizationInfo主要是用來處理權(quán)限配置,doGetAuthenticationInfo主要處理身份認(rèn)證。
這里在doGetAuthorizationInfo中,將role表的id和permission表的code分別設(shè)置到SimpleAuthorizationInfo對(duì)象中的role和permission中。
還有一個(gè)地方需要注意:@Component("authorizer"),剛開始我沒設(shè)置,但報(bào)錯(cuò)提示需要一個(gè)authorizer的bean,查看AuthorizingRealm可以發(fā)現(xiàn)它implements了Authorizer,所以在自定義的realm上添加@Component("authorizer")就可以了。
四、登錄認(rèn)證
1.登錄頁面
這里做了一個(gè)非常丑的登錄頁面,主要是自己懶,不想在網(wǎng)上復(fù)制粘貼找登錄頁面了。
2.處理登錄請(qǐng)求
在LoginController中通過登錄名、密碼獲取到token實(shí)現(xiàn)登錄。
五、Controller層訪問控制
1.首先來數(shù)據(jù)庫的數(shù)據(jù),兩張圖是用戶角色、和角色權(quán)限的數(shù)據(jù)。
2.設(shè)置權(quán)限
這里在用戶頁面點(diǎn)擊編輯按鈕時(shí)設(shè)置需要有id=002的角色,在點(diǎn)擊選擇角色按鈕時(shí)需要有code=002的權(quán)限。
@RequestMapping(value = "/edit",method = RequestMethod.GET) @RequiresRoles("002")//權(quán)限管理; public String editGet(Model model,@RequestParam(value="id") String id) { model.addAttribute("id", id); return "/user/edit"; }
@RequestMapping(value = "/selrole",method = RequestMethod.GET) @RequiresPermissions("002")//權(quán)限管理; public String selctRole(Model model,@RequestParam("id") String id,@RequestParam("type") Integer type) { model.addAttribute("id",id); model.addAttribute("type", type); return "/user/selrole"; }
當(dāng)使用用戶001登錄時(shí),點(diǎn)擊編輯,彈出框如下,提示沒有002的角色
點(diǎn)擊選擇角色按鈕時(shí)提示沒有002的權(quán)限。
當(dāng)使用用戶002登錄時(shí),點(diǎn)擊編輯按鈕,顯示正常,點(diǎn)擊選擇角色也是提示沒002的權(quán)限,因?yàn)闄?quán)限只有001。
六、前端頁面層訪問控制
有時(shí)為了不想像上面那樣彈出錯(cuò)誤頁面,需要在按鈕顯示上進(jìn)行不可見,這樣用戶也不會(huì)點(diǎn)擊到。前面已經(jīng)引入了依賴并配置了bean,這里測試下在html中使用shiro。
1.首先設(shè)置html標(biāo)簽引入shiro
<html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
2.控制按鈕可見
這里使用shiro:hasAnyRoles="002,003"判斷用戶角色是否是002或003,是則顯示不是則不顯示。
<div class="layui-inline"> <a shiro:hasAnyRoles="002,003" class="layui-btn layui-btn-normal newsAdd_btn" onclick="addUser('')">添加用戶</a> </div> <div class="layui-inline"> <a shiro:hasAnyRoles="002,003" class="layui-btn layui-btn-danger batchDel" onclick="getDatas();">批量刪除</a> </div>
當(dāng)001用戶登錄時(shí),添加用戶、批量刪除按鈕都不顯示,只顯示查詢按鈕。
當(dāng)002用戶登錄時(shí),添加用戶、批量刪除按鈕都顯示
七、小結(jié)
這里只是實(shí)現(xiàn)了Shiro的簡單的功能,Shiro還有很多很強(qiáng)大的功能,比如session管理等,而且目前權(quán)限管理模塊還有很多需要優(yōu)化的功能,左側(cè)導(dǎo)航欄的動(dòng)態(tài)加載和權(quán)限控制、Shiro與Redis結(jié)合實(shí)現(xiàn)session共享、Shiro與Cas結(jié)合實(shí)現(xiàn)單點(diǎn)登錄等。后續(xù)可以把項(xiàng)目做為開源項(xiàng)目,慢慢完善集成更多模塊例如:Swagger2、Redis、Druid、RabbitMQ等供初學(xué)者參考。
實(shí)際項(xiàng)目中,經(jīng)常需要用到角色權(quán)限區(qū)分,以此來為不同的角色賦予不同的權(quán)利,分配不同的任務(wù)。比如,普通用戶只能瀏覽;會(huì)員可以瀏覽和評(píng)論;超級(jí)會(huì)員可以瀏覽、評(píng)論和看視頻課等;實(shí)際應(yīng)用場景很多。毫不夸張的說,幾乎每個(gè)完整的項(xiàng)目都會(huì)涉及到權(quán)限管理。
因此,這篇文章,阿淼就帶大家將 shiro 權(quán)限框架整合到 SpringBoot 中,以達(dá)到快速的實(shí)現(xiàn)權(quán)限管理的功能。
在 Spring Boot 中做權(quán)限管理,一般來說,主流的方案是 Spring Security ,但是由于 Spring Security 過于龐大和復(fù)雜,只要能滿足業(yè)務(wù)需要,大多數(shù)公司還是會(huì)選擇 Apache Shiro 來使用。
一般來說,Spring Security 和 Shiro 的區(qū)別如下:
Spring SecurityApache Shiro重量級(jí)的安全管理框架輕量級(jí)的安全管理框架概念復(fù)雜,配置繁瑣概念簡單、配置簡單功能強(qiáng)大功能簡單
這篇文章會(huì)首先帶大家了解 Apache Shiro ,然后再給出使用案例 Demo。
照例又去官網(wǎng)扒了扒介紹:
Apache Shiro? is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications. Apache Shiro?是一個(gè)強(qiáng)大且易用的Java安全框架,能夠用于身份驗(yàn)證、授權(quán)、加密和會(huì)話管理。Shiro擁有易于理解的API,您可以快速、輕松地獲得任何應(yīng)用程序——從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序。
簡而言之,Apache Shiro 是一個(gè)強(qiáng)大靈活的開源安全框架,可以完全處理身份驗(yàn)證、授權(quán)、加密和會(huì)話管理。
對(duì)此,官方給出了詳細(xì)的解釋:shiro.apache.org/
自2003年以來,框架環(huán)境發(fā)生了很大變化,因此今天仍然有充分的理由使用Shiro。實(shí)際上有很多原因。Apache Shiro是:
Apache Shiro 是一個(gè)全面的、蘊(yùn)含豐富功能的安全框架。
下圖為描述 Shiro 功能的框架圖:
如圖所示,功能包括:
并且 Shiro 還有通過增加其他的功能來支持和加強(qiáng)這些不同應(yīng)用環(huán)境下安全領(lǐng)域的關(guān)注點(diǎn)。
特別是對(duì)以下的功能支持:
注意: Shiro 不會(huì)去維護(hù)用戶、維護(hù)權(quán)限,這些需要我們自己去設(shè)計(jì)/提供,然后通過相應(yīng)的接口注入給 Shiro
為方便我們初始化項(xiàng)目,Spring Boot給我們提供一個(gè)項(xiàng)目模板生成網(wǎng)站。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
復(fù)制代碼
web 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
復(fù)制代碼
shiro-spring 包就是此篇文章的核心
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
shiro 注解會(huì)用到 aop
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
數(shù)據(jù)庫相關(guān)包使用的是mybatisplus
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
復(fù)制代碼
建表語句在項(xiàng)目中有,項(xiàng)目地址: github.com/mmzsblog/mm…
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils
// .getSubject()).getServletRequest();//這個(gè)可以用來獲取在登錄的時(shí)候提交的其他額外的參數(shù)信息
String username = (String) principals.getPrimaryPrincipal();
// 受理權(quán)限
// 角色
Set<String> roles = new HashSet<String>();
Role role = roleService.getRoleByUserName(username);
System.out.println(role.getRoleName());
roles.add(role.getRoleName());
authorizationInfo.setRoles(roles);
// 權(quán)限
Set<String> permissions = new HashSet<String>();
List<Permission> querypermissions = permissionService.getPermissionsByRoleId(role.getId());
for (Permission permission : querypermissions) {
permissions.add(permission.getPermissionName());
}
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
String loginName = (String) authcToken.getPrincipal();
// 獲取用戶密碼
User user = userService.getOne(new QueryWrapper<User>().eq("username", loginName));
if (user == null) {
// 沒找到帳號(hào)
throw new UnknownAccountException();
}
String password = new String((char[]) authcToken.getCredentials());
String inpass = (new Md5Hash(password, user.getUsername())).toString();
if (!user.getPassword().equals(inpass)) {
throw new IncorrectCredentialsException();
}
// 交給AuthenticatingRealm使用CredentialsMatcher進(jìn)行密碼匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, user.getPassword(),
ByteSource.Util.bytes(loginName), getName());
return authenticationInfo;
}
}
復(fù)制代碼
@Configuration
public class ShiroConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
/**
* Shiro的Web過濾器Factory 命名:shiroFilter
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,這個(gè)屬性是必須的
shiroFilterFactoryBean.setSecurityManager(securityManager);
//需要權(quán)限的請(qǐng)求,如果沒有登錄則會(huì)跳轉(zhuǎn)到這里設(shè)置的url
shiroFilterFactoryBean.setLoginUrl("/login.html");
//設(shè)置登錄成功跳轉(zhuǎn)url,一般在登錄成功后自己代碼設(shè)置跳轉(zhuǎn)url,此處基本沒用
shiroFilterFactoryBean.setSuccessUrl("/main.html");
//設(shè)置無權(quán)限跳轉(zhuǎn)界面,此處一般不生效,一般自定義異常
shiroFilterFactoryBean.setUnauthorizedUrl("/error.html");
Map<String, Filter> filterMap = new LinkedHashMap<>();
// filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/*
* 定義shiro過濾鏈 Map結(jié)構(gòu)
* Map中key(xml中是指value值)的第一個(gè)'/'代表的路徑是相對(duì)于HttpServletRequest.getContextPath()的值來的
* anon:它對(duì)應(yīng)的過濾器里面是空的,什么都沒做,這里.do和.jsp后面的*表示參數(shù),比方說login.jsp?main這種
* authc:該過濾器下的頁面必須驗(yàn)證后才能訪問,它是Shiro內(nèi)置的一個(gè)攔截器org.apache.shiro.web.filter.authc.
* FormAuthenticationFilter
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/*
* 過濾鏈定義,從上向下順序執(zhí)行,一般將 /**放在最為下邊; authc:所有url都必須認(rèn)證通過才可以訪問;
* anon:所有url都都可以匿名訪問
*/
filterChainDefinitionMap.put("/login.html", "authc");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 權(quán)限管理
*/
@Bean
public SecurityManager securityManager() {
logger.info("=======================shiro=======================");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(MyShiroRealm());
// securityManager.setRememberMeManager(rememberMeManager);
return securityManager;
}
/**
* Shiro Realm 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗(yàn)證用戶登錄的類為自定義的
*/
@Bean
public MyShiroRealm MyShiroRealm() {
MyShiroRealm userRealm = new MyShiroRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 憑證匹配器 密碼驗(yàn)證
*/
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:這里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次數(shù),比如散列兩次,相當(dāng)于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(1);
// storedCredentialsHexEncoded默認(rèn)是true,此時(shí)用的是密碼加密用的是Hex編碼;false時(shí)用Base64編碼
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 開啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,并在必要時(shí)進(jìn)行安全邏輯驗(yàn)證
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
復(fù)制代碼
@RestController
public class UserController {
@PostMapping("login")
public String name(String username, String password) {
String result = "已登錄";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (!currentUser.isAuthenticated()) {
try {
currentUser.login(token);// 會(huì)觸發(fā)com.shiro.config.MyShiroRealm的doGetAuthenticationInfo方法
result = "登錄成功";
} catch (UnknownAccountException e) {
result = "用戶名錯(cuò)誤";
} catch (IncorrectCredentialsException e) {
result = "密碼錯(cuò)誤";
}
}
return result;
}
@GetMapping("logout")
public void logout() {
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
}
@RequiresPermissions("role:update")
@GetMapping("/role")
public String name() {
return "hello";
}
@RequiresPermissions("user:select")
@GetMapping("/role2")
public String permission() {
return "hello sel";
}
}
復(fù)制代碼
數(shù)據(jù)庫賬號(hào)(密碼經(jīng)過md5加鹽加密)
無權(quán)限時(shí)自定義了一個(gè)異常。所以,權(quán)限測試的時(shí)候沒有權(quán)限就會(huì)提示配置的提示語 “沒有權(quán)限”。
@ControllerAdvice
public class ShiroException {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public String name() {
return "沒有權(quán)限";
}
}
復(fù)制代碼
權(quán)限設(shè)置可在shiro配置類中shiro過濾鏈設(shè)置,也可用注解方式設(shè)置,本文使用注解方式。
shiro 的 session 和 cache 管理可以自定義,本文用的是默認(rèn)的,推薦自定義,方便管理。
hiro 1.7.0 發(fā)布,此版本包括自 1.6.0 版本以來解決的 7 個(gè)問題,現(xiàn)在可以從 maven 中央倉庫下載使用。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.7.0</version>
<type>pom</type>
</dependency>
值得注意的是:
關(guān)于 shiro 1.7 changelog 可以參考 issues.apache.org/jira
有關(guān) Shiro 的更多信息,請(qǐng)閱讀文檔 http://shiro.apache.org/documentation.html。
Apache Shiro 是一個(gè)強(qiáng)大且易用的 Java 安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼和會(huì)話管理。使用 Shiro 的易于理解的 API,您可以快速、輕松地獲得任何應(yīng)用程序,從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。