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 上下文对象
![](https://i-blog.csdnimg.cn/blog_migrate/8933b061f258db36f7e56947ac5d7b50.png)
(2) 查看实现类SecurityContextImpl
![](https://i-blog.csdnimg.cn/blog_migrate/a976f3fe6aa79b62b44ad0d96f59228f.png)
(3) 查看Authentication认证对象
Authencication 封装的就是用户信息。
![](https://i-blog.csdnimg.cn/blog_migrate/806394e03f1ec155492c44a6679d07f5.png)
(4) Authentication 实现类: UsernamePasswordAuthenticationToken
此类封装的就是用户密码的token信息
![](https://i-blog.csdnimg.cn/blog_migrate/bd8bdab15c7c51b4d589d932b016f2b3.png)
(5) 这里的principal就是指SysUser对象
![](https://i-blog.csdnimg.cn/blog_migrate/fff95fcae75e693544c91e8a092f863d.png)
(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}
![](https://i-blog.csdnimg.cn/blog_migrate/cc7043a6c44e08dd0f7191258939366e.png)
1.1.3.3 方式2:security标签
使用SpringSecurity标签获取用户名:
(1) 先引入标签库描述文件
![](https://i-blog.csdnimg.cn/blog_migrate/6951776750831ca2cf352b813b509873.png)
(2) 使用标签
<security:authentication property="principal.username" />
应用:
![](https://i-blog.csdnimg.cn/blog_migrate/ca38a17dd3ac4acdb0cb3b3c2ab07056.png)
1.2 给用户分配角色
1.2.1 需求分析
![](https://i-blog.csdnimg.cn/blog_migrate/b5b274e0931772f38bbcc47c72667dce.png)
-- 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) 访问用户列表
![](https://i-blog.csdnimg.cn/blog_migrate/8c45d1999980833cfb68c301c57488f3.png)
点击添加角色:
A. 提交到后台controller
B. 后台
a) 根据用户id查询SysUser用户
b) 获取用户已经具有的角色
String roleStr = “USER,ADMIN,”;
c) 查询所有角色
d) 保存
(2) 查看用户角色列表,如果用户已经有响应角色,就默认选中
![](https://i-blog.csdnimg.cn/blog_migrate/487f4afd02d4df3748890f84ef435c38.png)
页面判断:roleStr是否有包含(USER/ADMIN), 如果有包含,就选中
(3) 最后点击保存,给用户添加角色
1.2.3 进入用户角色列表页面
1.2.3.1 修改user-list.jsp提交地址
![](https://i-blog.csdnimg.cn/blog_migrate/d5e1aa389502a939c61f4dec4623e94e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/99b7fcfc4e46df99090349361e77e147.png)
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值,导致影响结果的封装。
解决如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a9c921788c789260d3384b91777f7623.png)
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"
οnclick="history.back(-1);">返回</button>
</div> <!--工具栏/--> </section> <!-- 正文区域 /--> |
1.2.4 保存
![](https://i-blog.csdnimg.cn/blog_migrate/72aa7501aad0710ef202328879ff3aad.png)
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表达式,所以需要开启表达式语言支持。
![](https://i-blog.csdnimg.cn/blog_migrate/4e231158149ed279046ca0ea55f2e747.png)
2.1.2 应用
2.1.2.1 期望结果
(1) 期望结果:不同的用户登陆只显示用户有权限的功能菜单
(2) 例如:Jack是普通用户登陆,
![](https://i-blog.csdnimg.cn/blog_migrate/98b9bee7ba8b1508768bff1afbfd2c75.png)
看到的就只有基础数据模块
(3) 例如:Rose是管理员登陆,
![](https://i-blog.csdnimg.cn/blog_migrate/7c353521b2871264e85cb91341eafc53.png)
看到的就是系统管理模块.
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登陆系统, 只能看到基础数据。
![](https://i-blog.csdnimg.cn/blog_migrate/56f806177bad64469005f0d7cdc0b184.png)
(2) 但是,直接在浏览器输入地址,也是可以访问角色管理模块的
![](https://i-blog.csdnimg.cn/blog_migrate/fb12afff149963e0fc0398aaaf8b70c9.png)
2.2.2 方式1:JSR-250注解方式权限校验
(1) 刚才菜单没有显示,如果直接访问地址栏,那么也会进入到具体的方法中.
(2) 现在要解决这个问题:使用一些注解来实现权限校验。
(3) 在讲各种注解之前,一定一定需要先配置AOP注解的支持,
而且一定需要在springmvc.xml配置文件中配置
(4) <!-- 开启AOP的支持 --> (5) <aop:aspectj-autoproxy proxy-target-class="true"/> |
(4) 完整应用
第一步:开启Aop注解支持
![](https://i-blog.csdnimg.cn/blog_migrate/de9c51d329a5036801e725743996de1b.png)
第二步:JSR-250注解方式权限拦截,需要添加依赖(已经完成)
![](https://i-blog.csdnimg.cn/blog_migrate/02d362d5eb82ed06bebe66efdd238f85.png)
第三步:在spring-security.xml配置文件中开启JSR-250的注解支持
<security:global-method-security jsr250-annotations="enabled"/> |
第四步:在Controller的类或者方法上添加注解@RolesAllowed
![](https://i-blog.csdnimg.cn/blog_migrate/968891f7c9fffb41d3be0822d62181a8.png)
2.2.3 方式2:SpringSecurity注解方式实现权限校验
(1) 在spring-security.xml配置文件中开启注解支持
<security:global-method-security secured-annotations="enabled"></security:global-method-security> |
(2) 在Controller类或者方法添加注解@Secured
![](https://i-blog.csdnimg.cn/blog_migrate/154859888f8371d1ecba51afffd0a795.png)
2.2.4 方式3:SpEL表达式方式实现权限校验
(1) 在spring-security.xml配置文件中开启注解支持
<!--方式3:spel表达式提供的权限校验支持--> <security:global-method-security pre-post-annotations="enabled"></security:global-method-security> |
(2) 在Controller类或者方法添加注解@PreAuthorize
3 系统日志功能
![](https://i-blog.csdnimg.cn/blog_migrate/98b7c231958b032496f62636a057ec82.png)
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 切面类
![](https://i-blog.csdnimg.cn/blog_migrate/be8aff8c92948295eec52607c1fd15ca.png)
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 注解扫描切面类
![](https://i-blog.csdnimg.cn/blog_migrate/67b01ae18d48d21060248e41ecd4f418.png)
3.6.4 访问controller测试
观察数据库中数据变化,
![](https://i-blog.csdnimg.cn/blog_migrate/c34a049117654d5f85a2401ed1c2d4b3.png)