shiro是一个易用的权限管理框架,只需提供一个Realm即可在项目中使用,本文就将结合上一篇中搭建的权限模块、角色模块和用户模块来搭建一个粗粒度的权限管理系统,具体如下:
1. 添加shiro依赖和与thymeleaf集成的依赖,并更新项目:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>1.0.2</version>
</dependency>
pom.xml
2. 添加自定义Realm类,主要完成用户校验和授权的功能
package com.lvniao.blog.config; import java.util.HashSet;
import java.util.Set; import javax.annotation.Resource; 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.authc.UsernamePasswordToken;
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.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import com.lvniao.blog.mapper.UserMapper;
import com.lvniao.blog.model.User; @Component
public class ShiroRealm extends AuthorizingRealm { @Autowired
private UserMapper userMapper; protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
User user = userMapper.getUserWithRoleByName(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet<String>();
Set<String> permissions = new HashSet<String>();
user.getRolesAndPermissions(roles, permissions);
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
} protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
String username = (String)usernamePasswordToken.getPrincipal();
String password = new String(usernamePasswordToken.getPassword());
if(userMapper.check(username, password) != null) {
User user = userMapper.getUserByName(username); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getName(),
user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName()); return authenticationInfo;
} else {
throw new AuthenticationException();
}
}
}
ShiroRealm
3. 添加ShiroConfiguration类,完成shiro的配置
package com.lvniao.blog.config; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
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 org.apache.shiro.mgt.SecurityManager; @Configuration
public class ShiroConfiguration{ @Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/admin/login"); shiroFilterFactoryBean.setSuccessUrl("/admin/"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/admin/loginaction", "anon"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
} @Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(ShiroRealm());
return securityManager;
} @Bean
public ShiroRealm ShiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
} @Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
ShiroConfiguration
因为添加了filterChainDefinitionMap.put("/**", "authc"),所以shiro会对所有请求进行验证,包括静态资源,所以需要通过如: filterChainDefinitionMap.put("/css/**", "anon");这样的来使静态资源请求不被拦截。通过这三个步骤就已经把shiro配置好了,下面就完成登录部分:
4. 添加登录action
package com.lvniao.blog.admin.controller; import java.util.List; 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.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView; import com.lvniao.blog.mapper.MenuMapper;
import com.lvniao.blog.mapper.UserMapper;
import com.lvniao.blog.model.Menu;
import com.lvniao.blog.model.User;
import com.lvniao.blog.util.Constant; @Controller
@RequestMapping("/admin")
public class AdminController { @Autowired
private UserMapper userMapper; @Autowired
private MenuMapper menuMapper; @RequestMapping("/")
public String index(Model model) {
model.addAttribute("menus", menuMapper.getParentMenu());
return "admin/index";
} @RequestMapping("/menu")
public String menu(Model model) {
return "admin/menu1";
} @RequestMapping("/login")
public String login() {
return "admin/login";
} @RequestMapping("/loginaction")
public ModelAndView loginAction(User user) {
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()) { try {
User u = userMapper.getUserByName(user.getName());
if(u != null) {
String password = new Md5Hash(user.getPassword(), u.getSalt()).toString();
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), password);
token.setRememberMe(false);
subject.login(token);
User currentUser = userMapper.getUserByName(user.getName());
subject.getSession().setAttribute(Constant.CurrentUser, currentUser);
return new ModelAndView("redirect:/admin/");
} } catch(UnknownAccountException ex) { } catch (IncorrectCredentialsException ex) { } catch (Exception ex) { }
}
return new ModelAndView("redirect:/admin/login");
}
}
AdminController
5. 添加登录页
<!DOCTYPE HTML>
<html>
<head>
<title>lvniao</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<link rel="stylesheet" th:href="@{/css/base.css}"/>
<link rel="stylesheet" th:href="@{/css/font-awesome.css}"/>
<link rel="stylesheet" th:href="@{/css/login.css}"/>
<script th:src="@{/js/jquery-3.2.1.js}"></script>
<script th:src="@{/statics/js/base.js}"></script>
<style>
*{
margin:0px;
padding:0px;
}
body{
background-image:url("/images/loginbg.jpg") ;
background-size:100% 100%;
}
</style>
</head>
<body>
<form class="login" th:action="@{/admin/loginaction}" th:method="post">
<div class="header">
<h1>登录</h1>
</div>
<div class="text name">
<input type="text" class="input" name="name" placeholder="用户名"/>
</div>
<div class="text password">
<input type="password" class="input" name="password" placeholder="密码"/>
</div>
<button type="submit" class="button">登录</button>
<div class="foot">
</div>
</form>
</body>
</html>
login.html
其中在css中使用url可以按照正常的使用,不用按照html中添加资源的方式。
登录成功后就会跳转到/admin/页
6. 因为在上一篇文章中以及添加好了角色授权和给用户赋角色,而现在要做的就是使用shiro来对界面做粗粒度权限管理,shiro在验证授权时需要获得用户的角色列表和权限列表,所以首先就修改User.java,增加获取角色和权限的接口,代码如下:
package com.lvniao.blog.model; import java.util.ArrayList;
import java.util.List;
import java.util.Set; import com.lvniao.blog.model.Permission;
import com.lvniao.blog.model.Role;
import com.lvniao.blog.util.StringUtil; public class User { private Long id; private String name; private String password; private String salt; private Boolean locked; private List<Role> roles = new ArrayList<Role>(); private String rolesStr = null; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public String getSalt() {
return salt;
} public void setSalt(String salt) {
this.salt = salt;
} public Boolean isLocked() {
return locked;
} public void setLocked(Boolean locked) {
this.locked = locked;
} public List<Role> getRoles() {
return roles;
} public void setRoles(List<Role> roles) {
this.roles = roles;
} public String getRolesStr() {
if(StringUtil.isNullOrEmpty(rolesStr)) {
if(getRoles() != null && getRoles().size() > 0) {
StringBuffer sb = new StringBuffer(); for(Role r : getRoles()) {
if(r != null) {
sb.append(r.getId());
sb.append(",");
}
}
if(sb.length() > 0) {
rolesStr = sb.toString().substring(0, sb.length() - 1);
}
}
}
return rolesStr;
} public void setRolesStr(String rolesStr) {
this.rolesStr = rolesStr;
} public void getRolesAndPermissions(Set<String> roleres, Set<String> permissionres) {
getRolesAndPermissions(getRoles(), roleres, permissionres);
} private void getRolesAndPermissions(List<Role> roles, Set<String> roleres, Set<String> permissionres) {
for(Role role : roles) {
roleres.add(role.getAlias());
for(Permission p : role.getPermissions()) {
permissionres.add(p.getAlias());
}
getRolesAndPermissions(role.getChildren(), roleres, permissionres);
}
}
}
User.java
使用时通过getRolesAndPermissions方法获得用户的角色集合和权限集合,已经在ShiroRealm中添加好了。然后就是在页面上使用权限管理;
7. 这儿以不同角色用户登录后,在用户管理界面上展示对操作权限的限制为例,用户管理界面的代码如下:
<!DOCTYPE html>
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8"/>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" th:href="@{/css/page.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/easyui/themes/default/easyui.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/easyui/themes/icon.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/layui/css/layui.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/grid.css}"/>
<script type="text/javascript" th:src="@{/easyui/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/easyui/jquery.easyui.min.js}"></script>
<script type="text/javascript" th:src="@{/layui/layui.js}"></script>
<script type="text/javascript" th:src="@{/layui/layui.all.js}"></script>
<script type="text/javascript" th:src="@{/js/form.js}"></script>
<script>
$(function(){
$("#grid").treegrid({url: '/user/list',
method: 'get',
rownumbers: true,
idField: 'id',
fit:true,
treeField: 'name',
pagination: true,
pageSize: 10,
pageList: [10, 50, 100]});
$("#btnSearch").click(function(){
$("#grid").treegrid({queryParams:form2Json("query")});
}); $(".lv-btn-group .lv-btn").click(function(){
var type = $(this).attr("type");
var addr = $(this).attr("addr");
if(type == "refresh"){
$(".lv-txtSearch").val("");
$("#grid").treegrid({queryParams:form2Json("query")});
return;
}
if(type == "add"){
layer.open({
type: 2,
title: $(this).attr("title"),
shadeClose: true,
shade: 0.8,
area: ['330px', '270px'],
content: addr,
success:function(layero){
$("#grid").treegrid({queryParams:form2Json("query")});
}
});
return;
} var row = $("#grid").treegrid("getSelected");
if(row == null){
layer.alert("请选择待操作的数据");
return;
}
if(type == "assign"){
layer.open({
type: 2,
title: $(this).attr("title"),
shadeClose: true,
shade: 0.8,
area: ['330px', '300px'],
content: addr + "/" + row.id,
success:function(layero){
$("#grid").treegrid({queryParams:form2Json("query")});
}
});
return;
}
if(type == "modify"){
layer.open({
type: 2,
title: $(this).attr("title"),
shadeClose: true,
shade: 0.8,
area: ['330px', '270px'],
content: addr + "/" + row.id,
success:function(layero){
$("#grid").treegrid({queryParams:form2Json("query")});
}
});
return;
}
if(type == "delete"){
$.ajax({
url:addr + "/" + row.id,
type:"get",
success:function(data){
$("#grid").treegrid({queryParams:form2Json("query")});
}
});
return;
} });
});
</script>
</head>
<body>
<div class="lv-option-container">
<form id="query" shiro:hasPermission="user:query" >
<input type="text" class="lv-txtSearch" name="query"/><div class="lv-btnSearch" id="btnSearch">查询</div>
</form> <div class="lv-btn-group">
<div class="lv-btn" type="refresh" shiro:hasPermission="user:refresh">刷新</div>
<div class="lv-btn" type="add" addr="/user/add" shiro:hasPermission="user:add" title="添加">添加</div>
<div class="lv-btn" type="modify" addr="/user/modify" shiro:hasPermission="user:modify" title="修改">修改</div>
<div class="lv-btn" type="assign" addr="/user/assign" shiro:hasPermission="user:assign" style="width:100px;" title="删除">分配角色</div>
<div class="lv-btn" type="delete" addr="/user/delete" shiro:hasPermission="user:delete" title="删除">删除</div>
</div>
</div>
<div class="lv-grid-container">
<table id="grid">
<thead>
<tr>
<th data-options="field:'name'" width="220">用户名</th>
<th data-options="field:'locked', formatter:booleanFormat" width="220">是否锁定</th>
</tr>
</thead>
</table>
</div>
</body>
</html>
index.html
首先要在HTML标记中添加<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">,然后在需要控制权限的界面元素上通过shiro:hasPermission="xxx"来验证当前用户是否有权限操作该元素,如上就完成了粗粒度的权限管理,下面是分别有所有权限和只有查询和刷新权限的界面:
代码下载路径:https://pan.baidu.com/s/1VqcDk9BIM10-HVyfmrjMHg, 密码:vxtg