1.pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<!--權限控制-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 頁面使用shiro標籤依賴 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>1.2.1</version>
</dependency>
2.config
package com.xlt.xfzb.web.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.xlt.xfzb.web.security.UserRealm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname ShiroConfiguration
* @Description 權限控制設置
* @Date 2020/3/27 17:47
* @Created by xm
*/
@Configuration
public class ShiroConfiguration {
// 創建自定義 realm
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
// 創建 SecurityManager 對象
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
// Filter工廠,設置對應的過濾條件和跳轉條件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* anon:匿名用戶可訪問
* authc:認證用戶可訪問
* user:使用rememberMe可訪問
* perms:對應權限可訪問
* role:對應角色權限可訪問
*/
Map<String, String> map = new HashMap<>();
// 開放登錄接口
map.put("/login", "anon");
map.put("/error", "anon");
map.put("/img/**","anon");
map.put("/css/**","anon");
map.put("/lib/**","anon");
map.put("/js/**","anon");
// 對所有用戶認證
map.put("/**", "authc");
// 登出
map.put("/logout", "logout");
// 登錄
// 注意:這裏配置的 /login 是指到 @RequestMapping(value="/login")中的 /login
shiroFilterFactoryBean.setLoginUrl("/login");
// 首頁
shiroFilterFactoryBean.setSuccessUrl("/index");
// 錯誤頁面,認證不通過跳轉
shiroFilterFactoryBean.setUnauthorizedUrl("/error/unAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
// 加入註解的使用,不加這個,註解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
// 跟上面的註解配置搭配使用,有時候加了上面的配置後註解不生效,需要加入下面的配置
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator app = new DefaultAdvisorAutoProxyCreator();
app.setProxyTargetClass(true);
return app;
}
//頁面標籤對象
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
3.依賴實體
用戶
package com.xlt.xfzb.web.entity;
import java.util.HashSet;
import java.util.Set;
/**
* @Classname User
* @Description 權限測試用戶
* @Date 2020/3/27 17:44
* @Created by xm
*/
public class UserEntity {
private String id;
private String name;
private String password;
//賬號
private String account;
//性別
private String sex;
//部門id
private String deptId;
//工號
private String work;
//自定義集合
private Set<RoleEntity> roles = new HashSet<RoleEntity>();
public String getId() {
return id;
}
public void setId(String 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 Set<RoleEntity> getRoles() {
return roles;
}
public void setRoles(Set<RoleEntity> roles) {
this.roles = roles;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getDeptId() {
return deptId;
}
public void setDeptId(String deptId) {
this.deptId = deptId;
}
public String getWork() {
return work;
}
public void setWork(String work) {
this.work = work;
}
}
角色
package com.xlt.xfzb.web.entity;
import java.util.HashSet;
import java.util.Set;
/**
* @Classname Role
* @Description TODO
* @Date 2020/3/30 11:42
* @Created by xm
*/
public class RoleEntity {
private String id;
//姓名
private String name;
//備註
private String remark;
//自定義菜單實體
private Set<PermissionEntity> permissions = new HashSet<>();//一個角色有多個權限
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<PermissionEntity> getPermissions() {
return permissions;
}
public void setPermissions(Set<PermissionEntity> permissions) {
this.permissions = permissions;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
權限
package com.xlt.xfzb.web.entity;
/**
* @Classname Permission
* @Description 權限實體
* @Date 2020/3/30 11:41
* @Created by xm
*/
public class PermissionEntity {
private String id;
private String name;
//路徑
private String url;
//描述
private String descrirtion;
//父id
private String pid;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDescrirtion() {
return descrirtion;
}
public void setDescrirtion(String descrirtion) {
this.descrirtion = descrirtion;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
}
4.自定義異常
package com.xlt.xfzb.web.security;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Classname NoPermissionException
* @Description TODO
* @Date 2020/3/31 11:16
* @Created by xm
*/
@ControllerAdvice
public class NoPermissionException {
// 授權失敗,就是說沒有該權限
@ExceptionHandler(UnauthorizedException.class)
public String handleShiroException(Exception ex) {
return "/error/unAuth";
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String AuthorizationException(Exception ex) {
return "權限認證失敗";
}
}
5.權限認證
package com.xlt.xfzb.web.security;
import com.netflix.discovery.provider.Serializer;
import com.xlt.xfzb.web.entity.PermissionEntity;
import com.xlt.xfzb.web.entity.RoleEntity;
import com.xlt.xfzb.web.entity.UserEntity;
import com.xlt.xfzb.web.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @Classname UserRealm
* @Description TODO
* @Date 2020/3/31 11:02
* @Created by xm
*/
@Service
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 用戶授權
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principalCollection) {
System.out.println("===執行授權===");
Subject subject = SecurityUtils.getSubject();
UserEntity user = (UserEntity)subject.getPrincipal();
if(user != null){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 角色字符串集合
Collection<String> rolesCollection = new HashSet<>();
// 權限字符串集合
Collection<String> premissionCollection = new HashSet<>();
// 讀取並賦值用戶角色與權限
Set<RoleEntity> roles = user.getRoles();
for(RoleEntity role : roles){
rolesCollection.add(role.getName());
Set<PermissionEntity> permissions = role.getPermissions();
for (PermissionEntity permission : permissions){
// 權限名稱爲PermissionEntity爲字段url
premissionCollection.add(permission.getUrl());
}
info.addStringPermissions(premissionCollection);
}
info.addRoles(rolesCollection);
return info;
}
return null;
}
/**
* 用戶認證
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("===執行認證===");
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
UserEntity bean = userService.findByName(token.getUsername());
if(bean == null){
// 用戶不存在
throw new UnknownAccountException();
} else {
bean = userService.findById(bean.getId());
if(null == bean) {
// 認證失敗
throw new AuthenticationException();
}
}
ByteSource credentialsSalt = ByteSource.Util.bytes(bean.getName());
return new SimpleAuthenticationInfo(bean, bean.getPassword(),
credentialsSalt, getName());
}
}
6.持久層
package com.xlt.xfzb.web.mapper;
import com.xlt.xfzb.web.entity.UserEntity;
import org.springframework.stereotype.Service;
/**
* @Classname UserMapper
* @Description 用戶測試
* @Date 2020/3/31 10:45
* @Created by xm
*/
@Service
public interface UserMapper {
// 根據用戶名稱,查詢用戶信息
UserEntity findByName(String name);
// 根據用戶id,查詢用戶信息、角色、權限
UserEntity findById(String id);
}
<?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.xlt.xfzb.web.mapper.UserMapper">
<resultMap id="userMap" type="com.xlt.xfzb.web.entity.UserEntity">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<collection property="roles" ofType="com.xlt.xfzb.web.entity.RoleEntity">
<id property="id" column="roleId"/>
<result property="name" column="roleName"/>
<collection property="permissions" ofType="com.xlt.xfzb.web.entity.PermissionEntity">
<id property="id" column="permissionId"/>
<result property="name" column="permissionName"/>
<result property="url" column="permissionUrl"/>
</collection>
</collection>
</resultMap>
<select id="findByName" parameterType="java.lang.String" resultType="com.xlt.xfzb.web.entity.UserEntity">
SELECT "id", "name", "password"
FROM "user"
WHERE "name" = #{name}
</select>
<select id="findById" parameterType="java.lang.String" resultMap="userMap">
SELECT u."id", u."name", u."password",
r."id" as roleId, r."name" as roleName,
p."id" as permissionId,
p."name" as permissionName,
p."url" as permissionUrl
FROM "user" u , "user_role" ur, "role" r, "role_permission" rp, "permission" p
WHERE u."id" = #{id}
AND u."id" = ur."user_id"
AND ur."role_id" = r."id"
AND r."id" = rp."role_id"
AND rp."permission_id" = p."id"
</select>
</mapper>
7.service
package com.xlt.xfzb.web.service;
import com.xlt.xfzb.web.entity.UserEntity;
/**
* @Classname UserService
* @Description 用戶相關業務接口
* @Date 2020/3/30 11:48
* @Created by xm
*/
public interface UserService {
/**
* 根據用戶名稱查詢用戶信息
* @param name
* @return
*/
UserEntity findByName(String name);
/**
* 根據用戶ID查詢用戶角色及權限
* @param id
* @return
*/
UserEntity findById(String id);
}
package com.xlt.xfzb.web.service.impl;
import com.xlt.xfzb.web.entity.UserEntity;
import com.xlt.xfzb.web.mapper.UserMapper;
import com.xlt.xfzb.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Classname UserService
* @Description 用戶相關業務接口實現
* @Date 2020/3/30 11:48
* @Created by xm
*/
@Service
public class UserServiceImpl implements UserService {
//測試用戶持久層
@Autowired
private UserMapper userMapper;
/**
* 根據用戶名稱查詢用戶信息
* @param name
* @return
*/
@Override
public UserEntity findByName(String name) {
return userMapper.findByName(name);
}
/**
* 根據用戶ID查詢用戶角色及權限
* @param id
* @return
*/
@Override
public UserEntity findById(String id) {
return userMapper.findById(id);
}
}
8.controller
package com.xlt.xfzb.web.controller;
import com.xlt.xfzb.web.entity.UserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Classname MainController
* @Description 主頁驗證前端控制器
* @Date 2020/3/31 11:18
* @Created by xm
*/
@Controller
public class MainController {
private static final Logger log=LoggerFactory.getLogger(MainController.class);
/**
* 主頁跳轉
* @param model 攜帶當前登錄用戶信息返回
* @return
*/
@RequestMapping("/index")
public String index(Model model){
UserEntity user = (UserEntity) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("uid",user.getId());
model.addAttribute("uname",user.getName());
log.info("當前登錄用戶-----"+user.getName());
return "index";
}
/**
* 登錄接口+登錄頁面
* @param request
* @param response
* @return
*/
@RequestMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response){
response.setHeader("root", request.getContextPath());
String userName = request.getParameter("username");
String password = request.getParameter("password");
// 等於null說明用戶沒有登錄,只是攔截所有請求到這裏,那就直接讓用戶去登錄頁面,就不認證了。
// 如果這裏不處理,那個會返回用戶名不存在,邏輯上不合理,用戶還沒登錄怎麼就用戶名不存在?
if(null == userName) {
return "login";
}
// 1.獲取Subject
Subject subject = SecurityUtils.getSubject();
// 2.封裝用戶數據
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
// 3.執行登錄方法
try{
subject.login(token);
return "redirect:/index";
} catch (UnknownAccountException e){
// 這裏是捕獲自定義Realm的用戶名不存在異常
log.info("用戶名不存在!");
} catch (IncorrectCredentialsException e){
log.info("密碼錯誤!");
} catch (AuthenticationException e) {
log.info("認證失敗!");
}
return "login";
}
/**
* 用戶登出操作
* @return
*/
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
if (subject != null) {
subject.logout();
}
return "login";
}
/**
* 錯誤頁面
* @return
*/
@RequestMapping("/error/unAuth")
public String unAuth(){
return "/error/unAuth";
}
@RequestMapping("/err")
public String err(){
return "/error/unAuth";
}
}
9.登錄頁面 login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
<h1>用戶登錄</h1>
<hr>
<form id="from" action="/login" method="post">
<table>
<tr>
<td>用戶名</td>
<td>
<input type="text" name="username" placeholder="請輸入賬戶名" value="" />
</td>
</tr>
<tr>
<td>密碼</td>
<td>
<input type="password" name="password" placeholder="請輸入密碼"/>
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="登錄"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
</form>
</body>
</html>
10.首頁 index.HTML
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<title>首頁</title>
</head>
<body>
<h1>首頁</h1>
<hr>
<ul>
<li><a href="user/index" >個人中心</a></li>
<li><a href="vip/index">會員中心</a></li>
<p th:text="'用戶編號:' + ${uid}"/>
<p th:text="'用戶姓名:' + ${uname}"/>
<shiro:hasPermission name="user1"><h2>admin</h2></shiro:hasPermission>
<shiro:hasPermission name="vip1"><h2>vip</h2></shiro:hasPermission>
<shiro:hasPermission name="svip1"><h2>admin</h2></shiro:hasPermission>
<li><a href="logout">退出登錄</a></li>
</ul>
</body>
</html>
11.數據庫結構
user
role
user_role
permission
role_permission
12. 頁面測試