1. 程式人生 > >SpringSecurity實現權限管理和頁面導航欄動態實現

SpringSecurity實現權限管理和頁面導航欄動態實現

tail ken eba javax head -h el表達式 eth tin

1 用戶模塊. 3

1.1 需求:獲取用戶名. 3

1.1.1 分析. 3

1.1.2 服務端獲取用戶信息. 4

1.1.3 頁面獲取用戶信息. 5

1.2 給用戶分配角色. 7

1.2.1 需求分析. 7

1.2.2 效果預覽. 7

1.2.3 進入用戶角色列表頁面. 9

1.2.4 保存. 12

2 SpringSecurity 授權功能. 13

2.1 在JSP頁面控制菜單權限. 13

2.1.1 基本語法. 13

2.1.2 應用. 14

2.2 服務端控制權限. 18

2.2.1 為什麽需要在服務端控制權限呢?. 18

2.2.2 方式1:JSR-250註解方式權限校驗. 19

2.2.3 方式2:SpringSecurity註解方式實現權限校驗. 20

2.2.4 方式3:SpEL表達式方式實現權限校驗. 20

3 系統日誌功能. 21

3.1 需求. 21

3.2 建表. 21

3.3 domain 21

3.4 dao 22

3.5 service 22

3.5.1 接口. 22

3.5.2 實現. 22

3.6 編寫切面類. 23

3.6.1 先再web.xml配置監聽器

. 23

3.6.2 切面類. 23

3.6.3 註解掃描切面類. 25

3.6.4 訪問controller測試. 25

1 用戶模塊

1.1 需求:獲取用戶名

1.1.1 分析

(1) SecurityContext 上下文對象

技術分享圖片

(2) 查看實現類SecurityContextImpl

技術分享圖片

(3) 查看Authentication認證對象

Authencication 封裝的就是用戶信息。

技術分享圖片

(4) Authentication 實現類: UsernamePasswordAuthenticationToken

此類封裝的就是用戶密碼的token信息

技術分享圖片

(5) 這裏的principal就是指SysUser對象

技術分享圖片

(6) Authentication 會封裝到SecurityContext中

(7) 然後,把SecurityContext綁定到當前線程上。

(8) 最後SecurityContext會存入HttpSession中

(9) 最終:session---àSecurityContext-----àAuthencication-------àSysUser

1.1.2 服務端獲取用戶信息

/**
*
查詢全部
*/
@RequestMapping("/findAll")
public ModelAndView findAll(
@RequestParam(required = true,defaultValue = "1") int pageNum,
@RequestParam(required = true,defaultValue = "2") int pageSize){

// 測試代碼
//1.
獲取綁定到當然線程上的SecurityContext
SecurityContext securityContext = SecurityContextHolder.getContext();
//2. 獲取認證器對象
Authentication authentication = securityContext.getAuthentication();
//3. 獲取認證身份信息. 註意這裏的user
// org.springframework.security.core.userdetails.User
User user = (User) authentication.getPrincipal();

//4. 獲取用戶名
System.out.println(sysUser.getUsername());


// 調用service查詢
List<SysUser> userList = userService.findAll(pageNum,pageSize);
// 封裝分頁bean
PageInfo<SysUser> pageInfo = new PageInfo<>(userList);

ModelAndView mv = new ModelAndView();
mv.setViewName("user-list");
mv.addObject("pageInfo",pageInfo);
return mv;
}

1.1.3 頁面獲取用戶信息

1.1.3.1 如何獲取?

(1) SecurityContext對象會存入到HttpSession對象中,怎麽證明這一點呢?

我們只需要在任意一個jsp頁面寫上${sessionScope}就可以看到session裏面的全部內容。

{SPRING_SECURITY_CONTEXT=

org.springframework.security.core.context.SecurityContextImpl@443cd45f: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@443cd45f: Principal: org.springframework.security.core.userdetails.User@231bff: Username: Jack; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 02D659915CA438267B59625611AB657A; Granted Authorities: ROLE_USER}

1.1.3.2 方式1:EL

在jsp頁面獲取用戶名,使用EL表達式:

${sessionScope.

SPRING_SECURITY_CONTEXT.authentication.principal.username}

技術分享圖片

1.1.3.3 方式2:security標簽

使用SpringSecurity標簽獲取用戶名:

(1) 先引入標簽庫描述文件

技術分享圖片

(2) 使用標簽

<security:authentication property="principal.username" />

應用:

技術分享圖片

1.2 給用戶分配角色

1.2.1 需求分析

技術分享圖片

-- 1.2 給用戶分配角色

-- 需求:給userId=1就是jack分配:普通用戶、管理員角色

-- 實現:

-- 1.先根據用戶id,刪除用戶角色表中數據

DELETE FROM sys_user_role WHERE userId=1;

-- 2.再給用戶分配角色

INSERT INTO sys_user_role(userId,roleId)VALUES(1,1);

INSERT INTO sys_user_role(userId,roleId)VALUES(1,2);

1.2.2 效果預覽

(1) 訪問用戶列表

技術分享圖片

點擊添加角色:

A. 提交到後臺controller

B. 後臺

a) 根據用戶id查詢SysUser用戶

b) 獲取用戶已經具有的角色

String roleStr = “USER,ADMIN,”;

c) 查詢所有角色

d) 保存

(2) 查看用戶角色列表,如果用戶已經有響應角色,就默認選中

技術分享圖片

頁面判斷:roleStr是否有包含(USER/ADMIN), 如果有包含,就選中

(3) 最後點擊保存,給用戶添加角色

1.2.3 進入用戶角色列表頁面

1.2.3.1 修改user-list.jsp提交地址

技術分享圖片

技術分享圖片

1.2.3.2 controller

l 實現思路

(1) 需要查詢用戶

(2) 查詢用戶角色

(3) 查詢所有角色

(4) 保存以上信息,頁面回顯

(5) 註意:打斷點調試sysUser中的用戶id是否有數據.

/**
*
用戶角色
* (1)
進入用戶角色user-role-add.jsp頁面
*/
@RequestMapping("/toUserRole")
public ModelAndView toUserRole(Long id){
// a.查詢用戶
SysUser sysUser = userService.findById(id);

// b.用戶具有的角色
List<Role> roles = sysUser.getRoles();
StringBuffer sb = new StringBuffer();
String roleStr="";
if (roles != null && roles.size()>0){
for(Role r : roles){
sb.append(r.getRoleName()+",");
}// 去除最後逗號
roleStr = sb.substring(0,sb.length()-1);
}

// c.查詢所有角色
List<Role> roleList = roleService.findAll();

// 返回結果封裝
ModelAndView mv = new ModelAndView();
mv.addObject("user",sysUser);
mv.addObject("roleStr",roleStr);
mv.addObject("roleList",roleList);
mv.setViewName("user-role-add");
return mv;
}

(6) 寫完上面代碼,調試發現根據用戶的id查詢的SysUser對象中主鍵值為空,因為查詢結果與延遲加載查詢結果都有相同的id值,導致影響結果的封裝。

解決如下:

技術分享圖片

1.2.3.3 user-role-add.jsp頁面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!-- 正文區域 -->
<section class="content"> <input type="hidden" name="userId"
value="${user.id}">
<table id="dataList"
class="table table-bordered table-striped table-hover dataTable">
<thead>
<tr>
<th class="" style="padding-right: 0px">
<input id="selall"
type="checkbox" class="icheckbox_square-blue"></th>
<th class="sorting_asc">ID</th>
<th class="sorting">角色名稱</th>
<th class="sorting">角色描述</th>
</tr>
</thead>
<tbody>
<c:forEach items="${roleList}" var="role">
<tr>

<td><input
name="ids" 判斷roleStr中是否有包含role.roleName
${fn:contains(roleStr,role.roleName )? ‘checked‘:‘‘}
type="checkbox"
value="${role.id}"></td>
<td>${role.id}</td>
<td>${role.roleName }</td>
<td>${role.roleDesc}</td>

</tr>
</c:forEach>
</tbody>

</table>
<!--訂單信息/--> <!--工具欄-->
<div class="box-tools text-center">
<button type="submit" class="btn bg-maroon">保存</button>
<button type="button" class="btn bg-default"
onclick="history.back(-1);">返回</button>
</div>
<!--工具欄/--> </section>
<!-- 正文區域 /-->

1.2.4 保存

技術分享圖片

1.2.4.1 controller

/**
*
用戶角色
* (2)
給用戶添加角色
*/
@RequestMapping("/addRoleToUser")
public String addRoleToUser(Long userId,Long[] ids){
userService.addRoleToUser(userId,ids);
return "redirect:/user/findAll";
}

1.2.4.2 service

n 先解除關系,刪除中間表數據

n 再往中間表添加數據,即給用戶添加角色關系維護

@Override
public void addRoleToUser(Long userId, Long[] roleIds) {
//1. 先刪除中間表當前用戶相關的角色數據
//DELETE FROM sys_user_role WHERE userId=1
userDao.deleteUserRole(userId);

//2. 添加用戶角色關系
if (roleIds != null &&roleIds.length>0){
for(Long roleId : roleIds){
userDao.addUserRole(userId,roleId);
}
}
}

1.2.4.3 dao

/**
*
根據用戶刪除用戶角色關聯表數據
* @param
userId 用戶主鍵
*/
@Select("delete from sys_user_role where userId=#{userId}")
void deleteUserRole(Long userId);

/**
*
添加用戶角色關系
* @param
userId 用戶主鍵
* @param
roleId 角色主鍵
*/
@Insert("insert into sys_user_role(userId,roleId)values(#{userId},#{roleId})")
void addUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);

2 SpringSecurity 授權功能

2.1 在JSP頁面控制菜單權限

2.1.1 基本語法

(1) 頁面引入標簽庫文件

<%@taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

(2) 使用標簽

<security:authorize access="hasAnyRole(‘ROLE_ADMIN‘)">
標簽訪問資源需要ROLE_ADMIN角色權限。
 

(3) 註意

因為這裏使用的是SPEL表達式,所以需要開啟表達式語言支持。

技術分享圖片

2.1.2 應用

2.1.2.1 期望結果

(1) 期望結果:不同的用戶登陸只顯示用戶有權限的功能菜單

(2) 例如:Jack是普通用戶登陸,

技術分享圖片

看到的就只有基礎數據模塊

(3) 例如:Rose是管理員登陸,

技術分享圖片

看到的就是系統管理模塊.

2.1.2.2 頁面實現

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel -->
<div class="user-panel">
<div class="pull-left image">
<img src="${pageContext.request.contextPath}/img/user2-160x160.jpg"
class="img-circle" alt="User Image">
</div>
<div class="pull-left info">
<p>
<security:authentication property="principal.username" />
</p>
<a href="#"><i class="fa fa-circle text-success"></i> 在線</a>
</div>
</div>

<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu">
<li class="header">菜單</li>
<li id="admin-index"><a
href="${pageContext.request.contextPath}/pages/main.jsp"><i
class="fa fa-dashboard"></i> <span>首頁</span></a></li>

<li class="treeview">
<security:authorize access="hasAnyRole(‘ROLE_ADMIN‘)">
<a href="#"> <i class="fa fa-cogs"></i>
<span>系統管理</span> <span class="pull-right-container"> <i
class="fa fa-angle-left pull-right"></i>
</span>
</a>
</security:authorize>
<ul class="treeview-menu">
<security:authorize access="hasAnyRole(‘ROLE_ADMIN‘)">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/user/findAll"> <i
class="fa fa-circle-o"></i> 用戶管理
</a></li>
</security:authorize>
<security:authorize access="hasAnyRole(‘ROLE_ADMIN‘)">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/role/findAll"> <i
class="fa fa-circle-o"></i> 角色管理
</a></li>
</security:authorize>
<security:authorize access="hasAnyRole(‘ROLE_ADMIN‘)">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/permission/findAll">
<i class="fa fa-circle-o"></i> 權限管理
</a></li>
</security:authorize>
<security:authorize access="hasAnyRole(‘ROLE_ADMIN‘)">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/pages/syslog-list.jsp"> <i
class="fa fa-circle-o"></i> 訪問日誌
</a></li>
</security:authorize>
</ul></li>


<li class="treeview">
<security:authorize access="hasAnyRole(‘ROLE_USER‘)">
<a href="#">
<i class="fa fa-cube"></i>
<span>基礎數據</span> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
</security:authorize>

<ul class="treeview-menu">

<security:authorize access="hasAnyRole(‘ROLE_USER‘)">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/product/findByPage">
<i class="fa fa-circle-o"></i> 產品管理
</a></li>
</security:authorize>

<security:authorize access="hasAnyRole(‘ROLE_USER‘)">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/order/findByPage">
<i class="fa fa-circle-o"></i> 訂單管理
</a></li>
</security:authorize>

</ul>
</li>

</ul>
</section>
<!-- /.sidebar -->
</aside>

2.2 服務端控制權限

2.2.1 為什麽需要在服務端控制權限呢?

(1) 例如普通用戶jack登陸系統, 只能看到基礎數據。

技術分享圖片

(2) 但是,直接在瀏覽器輸入地址,也是可以訪問角色管理模塊的

技術分享圖片

2.2.2 方式1:JSR-250註解方式權限校驗

(1) 剛才菜單沒有顯示,如果直接訪問地址欄,那麽也會進入到具體的方法中.

(2) 現在要解決這個問題:使用一些註解來實現權限校驗。

(3) 在講各種註解之前,一定一定需要先配置AOP註解的支持,

而且一定需要在springmvc.xml配置文件中配置

(4) <!-- 開啟AOP的支持 -->

(5) <aop:aspectj-autoproxy proxy-target-class="true"/>

(4) 完整應用

第一步:開啟Aop註解支持

技術分享圖片

第二步:JSR-250註解方式權限攔截,需要添加依賴(已經完成)

技術分享圖片

第三步:在spring-security.xml配置文件中開啟JSR-250的註解支持

<security:global-method-security jsr250-annotations="enabled"/>

第四步:在Controller的類或者方法上添加註解@RolesAllowed

技術分享圖片

2.2.3 方式2:SpringSecurity註解方式實現權限校驗

(1) 在spring-security.xml配置文件中開啟註解支持

<security:global-method-security secured-annotations="enabled"></security:global-method-security>

(2) 在Controller類或者方法添加註解@Secured

技術分享圖片

2.2.4 方式3:SpEL表達式方式實現權限校驗

(1) 在spring-security.xml配置文件中開啟註解支持

<!--方式3spel表達式提供的權限校驗支持-->
<security:global-method-security pre-post-annotations="enabled"></security:global-method-security>

(2) 在Controller類或者方法添加註解@PreAuthorize

3 系統日誌功能

技術分享圖片

3.1 需求

希望系統自動記錄訪問後臺controller的時間、方法、來訪者ip等信息。

3.2 建表

CREATE TABLE sys_log(

id int PRIMARY KEY,

visitTime DATE,

username VARCHAR2(50),

ip VARCHAR2(30),

method VARCHAR2(200)

)

3.3 domain

public class SysLog {
private Long id;
private Date visitTime;
private String username;
private String ip;
private String method;

3.4 dao

public interface ISysLogDao {
@Insert("insert into sys_log(id,visitTime,username,ip,method) values(seq_log.nextval(),#{visitTime},#{username},#{ip},#{method})")
void save(SysLog log);
}

3.5 service

3.5.1 接口

public interface ISysLogService {
void save(SysLog sysLog);
}

3.5.2 實現

@Service
@Transactional
public class SysLogServiceImpl implements ISysLogService {
@Autowired
private ISysLogDao sysLogDao;
@Override
public void save(SysLog sysLog) {
sysLogDao.save(sysLog);
}
}

3.6 編寫切面類

3.6.1 先再web.xml配置監聽器

<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

然後再切面類中就可以註入HttpServletRequest對象,獲取來訪者ip。

3.6.2 切面類

技術分享圖片

package com.itheima.utils;

import com.itheima.domain.SysLog;
import com.itheima.service.ISysLogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Component
@Aspect
public class LogAop {

// 註入request對象(需要配置監聽器)
@Autowired
private HttpServletRequest request;

@Autowired
private ISysLogService sysLogService;

@Around("execution(* com.itheima.controller.*Controller.*(..))")
public Object arount(ProceedingJoinPoint pjp) {
//1. 獲取方法參數
Object[] methodArgs = pjp.getArgs();
//2. 獲取正在執行的類和方法
//pjp.getSignature().getName();
方法名稱
String methodName = pjp.getSignature().toShortString();
//3. 獲取用戶
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
User user = (User) authentication.getPrincipal();
String username = user.getUsername();
//4. 獲取ip
String remoteAddr = request.getRemoteAddr();
//5. 封裝
SysLog log = new SysLog();
log.setIp(remoteAddr);
log.setMethod(methodName);
log.setUsername(username);
log.setVisitTime(new Date());

try {
// 放行,去到控制器處理請求的方法
Object returnValue = pjp.proceed(methodArgs);
//方法執行後增強: 記錄日誌
sysLogService.save(log);
return returnValue;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}

3.6.3 註解掃描切面類

技術分享圖片

3.6.4 訪問controller測試

觀察數據庫中數據變化,

技術分享圖片

SpringSecurity實現權限管理和頁面導航欄動態實現