如何實現(xiàn)登錄、URL 和頁面按鈕的訪問控制
來源:社會主義接班人
cnblogs.com/5ishare/p/10461073.html
一、引入依賴
二、增加 Shiro 配置
三、自定義 Realm
四、登錄認(rèn)證
五、Controller 層訪問控制
六、前端頁面層訪問控制
七、小結(jié)
用戶權(quán)限管理一般是對用戶頁面、按鈕的訪問權(quán)限管理。Shiro 框架是一個強大且易用的 Java 安全框架,執(zhí)行身份驗證、授權(quán)、密碼和會話管理,對于 Shiro 的介紹這里就不多說。本篇博客主要是了解 Shiro 的基礎(chǔ)使用方法,在權(quán)限管理系統(tǒng)中集成 Shiro 實現(xiàn)登錄、url 和頁面按鈕的訪問控制。
一、引入依賴
使用 SpringBoot 集成 Shiro 時,在 pom.xml 中可以引入 shiro-spring-boot-web-starter。由于使用的是 thymeleaf 框架,thymeleaf 與 Shiro 結(jié)合需要 引入 thymeleaf-extras-shiro。
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
二、增加 Shiro 配置
有哪些 url 是需要攔截的,哪些是不需要攔截的,登錄頁面、登錄成功頁面的 url、自定義的 Realm 等這些信息需要設(shè)置到 Shiro 中,所以創(chuàng)建 Configuration 文件 ShiroConfig。
package com.example.config;
import org.apache.shiro.mgt.SecurityManager;
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 at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//攔截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不會被攔截的鏈接 順序判斷
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實現(xiàn)了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了;
//<!-- authc:所有url都必須認(rèn)證通過才可以訪問; anon:所有url都都可以匿名訪問-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不設(shè)置默認(rèn)會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登錄成功后要跳轉(zhuǎn)的鏈接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授權(quán)界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean() //創(chuàng)建DefaultWebSecurityManager
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")MyShiroRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
//創(chuàng)建Realm
@Bean()
public MyShiroRealm getUserRealm(){
return new MyShiroRealm();
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
ShiroDialect 這個 bean 對象是在 thymeleaf 與 Shiro 結(jié)合,前端 html 訪問 Shiro 時使用。
三、自定義 Realm
在自定義的 Realm 中繼承了 AuthorizingRealm 抽象類,重寫了兩個方法:doGetAuthorizationInfo 和 doGetAuthenticationInfo。doGetAuthorizationInfo 主要是用來處理權(quán)限配置,doGetAuthenticationInfo 主要處理身份認(rèn)證。
這里在 doGetAuthorizationInfo 中,將 role 表的 id 和 permission 表的 code 分別設(shè)置到 SimpleAuthorizationInfo 對象中的 role 和 permission 中。
還有一個地方需要注意:@Component("authorizer"),剛開始我沒設(shè)置,但報錯提示需要一個 authorizer 的 bean,查看 AuthorizingRealm 可以發(fā)現(xiàn)它 implements 了 Authorizer,所以在自定義的 realm 上添加 @Component("authorizer") 就可以了。
package com.example.config;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.pojo.Permission;
import com.example.pojo.Role;
import com.example.pojo.User;
import com.example.service.RoleService;
import com.example.service.UserService;
@Component("authorizer")
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("權(quán)限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User user = (User)principals.getPrimaryPrincipal();
System.out.println("User:"+user.toString()+" roles count:"+user.getRoles().size());
for(Role role:user.getRoles()){
authorizationInfo.addRole(role.getId());
role=roleService.getRoleById(role.getId());
System.out.println("Role:"+role.toString());
for(Permission p:role.getPermissions()){
System.out.println("Permission:"+p.toString());
authorizationInfo.addStringPermission(p.getCode());
}
}
System.out.println("權(quán)限配置-->authorizationInfo"+authorizationInfo.toString());
return authorizationInfo;
}
/*主要是用來進行身份認(rèn)證的,也就是說驗證用戶輸入的賬號和密碼是否正確。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//獲取用戶的輸入的賬號.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通過username從數(shù)據(jù)庫中查找 User對象,如果找到,沒找到.
//實際項目中,這里可以根據(jù)實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法
User user = userService.getUserById(username);
System.out.println("----->>userInfo="+user);
if(user == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用戶名
"123456", //密碼
getName() //realm name
);
return authenticationInfo;
}
}
四、登錄認(rèn)證
登錄頁面
這里做了一個非常丑的登錄頁面,主要是自己懶,不想在網(wǎng)上復(fù)制粘貼找登錄頁面了。
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<meta >
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta >
<meta >
<meta >
<meta >
</head>
<form action="/login" method="post">
<label>用戶名:</label><input type="text" ><br>
<label >密碼:</label><input type="text" ><br>
<button type="submit">登錄</button><button type="reset">取消</button>
</form>
</body>
</html>
處理登錄請求
在 LoginController 中通過登錄名、密碼獲取到 token 實現(xiàn)登錄。
package com.example.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class LoginController {
//退出的時候是get請求,主要是用于退出
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login(){
return "login";
}
//post登錄
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(Model model,String id,String pwd){
//添加用戶認(rèn)證信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
id,
"123456");
try {
subject.login(usernamePasswordToken);
return "home";
}
catch (UnknownAccountException e) {
//用戶名不存在
model.addAttribute("msg","用戶名不存在");
return "login";
}catch (IncorrectCredentialsException e) {
//密碼錯誤
model.addAttribute("msg","密碼錯誤");
return "login";
}
}
@RequestMapping(value = "/index")
public String index(){
return "home";
}
}
五、Controller 層訪問控制
首先來數(shù)據(jù)庫的數(shù)據(jù),兩張圖是用戶角色、和角色權(quán)限的數(shù)據(jù)。


設(shè)置權(quán)限
這里在用戶頁面點擊編輯按鈕時設(shè)置需要有 id=002 的角色,在點擊選擇角色按鈕時需要有 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 登錄時,點擊編輯,彈出框如下,提示沒有 002 的角色

點擊選擇角色按鈕時提示沒有 002 的權(quán)限。

當(dāng)使用用戶 002 登錄時,點擊編輯按鈕,顯示正常,點擊選擇角色也是提示沒 002 的權(quán)限,因為權(quán)限只有 001。
六、前端頁面層訪問控制
有時為了不想像上面那樣彈出錯誤頁面,需要在按鈕顯示上進行不可見,這樣用戶也不會點擊到。前面已經(jīng)引入了依賴并配置了 bean,這里測試下在 html 中使用 shiro。
首先設(shè)置 html 標(biāo)簽引入 shiro
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
控制按鈕可見
這里使用 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 用戶登錄時,添加用戶、批量刪除按鈕都不顯示,只顯示查詢按鈕。

當(dāng) 002 用戶登錄時,添加用戶、批量刪除按鈕都顯示

七、小結(jié)
這里只是實現(xiàn)了 Shiro 的簡單的功能,Shiro 還有很多很強大的功能,比如 session 管理等,而且目前權(quán)限管理模塊還有很多需要優(yōu)化的功能,左側(cè)導(dǎo)航欄的動態(tài)加載和權(quán)限控制、Shiro 與 Redis 結(jié)合實現(xiàn) session 共享、Shiro 與 Cas 結(jié)合實現(xiàn)單點登錄等。后續(xù)可以把項目做為開源項目,慢慢完善集成更多模塊例如:Swagger2、Redis、Druid、RabbitMQ 等供初學(xué)者參考。
怎么接私活?這個渠道你100%有用!請收藏!
喜歡文章,點個在看

