当前位置 : 主页 > 编程语言 > java >

SSM综合案例之动态权限实战教程

来源:互联网 收集:自由互联 发布时间:2022-10-26
一、课程目标 1. 【了解】动态权限配置 2. 【掌握】AOP日志管理 二、动态权限 2.1 将公开权限设置为无需认证即可访问 !-- 配置不拦截的资源 -- security:http pattern = "/login.jsp" security = "non

一、课程目标

1. 【了解】动态权限配置
2. 【掌握】AOP日志管理

二、动态权限

2.1 将公开权限设置为无需认证即可访问

<!-- 配置不拦截的资源 -->
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/layui/**" security="none"/>

2.2 配置具体的规则

<!--
配置具体的规则
auto-config="true" 不用自己编写登录的页面,框架提供默认登录页面
use-expressions="true" 是否使用SPEL表达式(否则只能使用USER_角色的形式配置)
-->
<security:http auto-config="true" use-expressions="true">
<!-- 同源策略 如果页面使用iframe需要配置 否则不能使用 -->
<security:headers>
<security:frame-options disabled="true"/>
</security:headers>

<!-- 定义跳转的具体的页面 -->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/users/name"
/>
<security:intercept-url pattern="/**" access="isAuthenticated()"/>
<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="isAuthenticated()"
<!-- 权限框架本质是过滤器链 会依次进行权限验证 如果验证通过继续执行
该配置 配置的是 除以上不拦截的资源外 所有url请求必须拥有认证的权限(登录后才能访问)
-->
<!-- 关闭跨域请求 -->
<security:csrf disabled="true"/>
<!-- 退出 -->
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" />
</security:http>

2.3 设置权限数据

2.4 自定义相关过滤器与决策器

自定义决策管理器

权限框架本身是由多个不同功能的过滤器组成的,不同的过滤器负责不同的功能例如认证过滤、静态资源过滤、等 授权过滤也是一样,决策器就是同于判断当前请求的url当前账号是否拥有权限

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Iterator;
@Service
//决策管理器
public class MyAccessDecisionManager implements AccessDecisionManager {

// decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {

if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute c;
String needRole;
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
}

@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}

@Override
public boolean supports(Class<?> clazz) {
return true;
}
}

自定义权限加载器

import com.yunhe.javabean.Permission;
import com.yunhe.mapper.PermissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

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

@Service
//授权管理器
//用于当前项目要动态配置的权限信息
//从数据库中取出所有的权限信息 进行配置
//这样当客户请求对应权限时进行权限验证(因为不可能将所有的url都过滤)
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {

@Autowired
//注入权限查询的dao层
private PermissionMapper permissionMapper;

private HashMap<String, Collection<ConfigAttribute>> map =null;

/**
* 加载权限表中所有权限
*/
public void loadResourceDefine(){
map = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
//动态查询当前数据库中所有的权限
List<Permission> permissions = permissionMapper.selectAll();
for(Permission permission : permissions) {
array = new ArrayList<>();
cfg = new SecurityConfig(permission.getPermissionName());
//此处只添加了权限的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
array.add(cfg);
//用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
//实际加载存储的结构为 url->[name1,name2....]
//url是进行请求url拦截使用的 name是进行权限验证使用的
//也就是说在用户进行授权时 实际加载的是权限名称

map.put(permission.getUrl(), array);
}

}

//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if(map ==null) loadResourceDefine();
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}

@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

@Override
public boolean supports(Class<?> clazz) {
return true;
}
}

自定义权限拦截器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import javax.servlet.*;
import java.io.IOException;

@Service
//自定义权限拦截器
//FilterSecurityInterceptor是权限框架中用于处理权限验证的过滤器
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {


//使用自己定义权限加载器
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;

//使用自定义的决策管理器
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}


@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}


public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}

@Override
public void destroy() {

}

@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}

@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}

2.5 将自定义权限拦截器配置入权限框架中

在security:http标签中添加

<!-- 配置自定义拦截器 将定义拦截器配置到当前权限框架拦截器链中的指定位置
将其配置在本身的权限认证过滤器之后执行
-->
<security:custom-filter ref="myFilterSecurityInterceptor" after="FILTER_SECURITY_INTERCEPTOR"></security:custom-filter>

2.6 配置授权页面

在配置后使用账号登录会出现没有权限403代码页面

如果用户登录进行操作时,直接报403错误用户体检并不好,我们可以制作一个比较好看的页面,告诉用户您用户不足,请联系管理员!

在web.xml中配置

<error-page>
<error-code>403</error-code>
<location>/failer.jsp</location>
</error-page>

2.7 授权

在书写权限加载器时,我们发现,加载器加载url是用于http请求的过滤匹配,而实际进行权限验证使用的是权限名称,为了方便书写,我们在登录认证时查询权限信息进行授权操作

修改PermissionMapper

//根据用户id查询权限数据
public List<Permission> selectByUid(int uid);<?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.yunhe.mapper.PermissionMapper">
<select id="selectByUid" resultType="com.yunhe.javabean.Permission">
select p.*
from users u,role r,permission p,users_role ur,role_permission rp
where u.id=ur.userId and r.id=ur.roleId and r.id=rp.roleId and p.id =rp.permissionId and u.id=#{uid}
</select>
</mapper>

修改PermissionService

//根据uid查询权限数据
public List<Permission> findByUid(int uid);@Override
public List<Permission> findByUid(int uid) {
return permissionMapper.selectByUid(uid);
}

修改userService

将我们之前书写写死的角色认证与权限认证查询数据库的形式添加

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询数据返回实体类对应数据
Users users = userMapper.selectByUserName(username);
if (users != null) {
//创建springSecurity主体中存储的账号对象并返回(账号,密码,权限列表)
UserDetails userDetails = new User(users.getUsername(), users.getPassword(), getAuthority(users.getId()));
return userDetails;
}
return null;
}

@Autowired
PermissionService permissionService;
//获取权限列表
public List<GrantedAuthority> getAuthority(int uid) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
//查询指定用户权限列表
List<Permission> permissionList = permissionService.findByUid(uid);
for (Permission p:permissionList ) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(p.getPermissionName());
grantedAuthorities.add(grantedAuthority);
}
return grantedAuthorities;
}

三、AOP日志管理

3.1 数据库与表结构

日志表信息描述sysLog

序号

字段名称

字段类型

字段描述

1

id

VARCHAR(32)

主键 无意义uuid

2

vistTime

TIMESTAMP

访问时间

3

username

VARCHAR(32)

操作用户

4

ip

VARCHAR(50)

访问ip

5

url

VARCHAR(50)

访问资源url

6

executionTime

INT

执行时长

7

className

VARCHAR(50)

访问方法所在类

8

methodName

VARCHAR(50)

访问方法

9

args

VARCHAR(50)

访问方法参数列表

sql语句

CREATE TABLE `syslog` (
`id` int(70) NOT NULL AUTO_INCREMENT,
`visitTime` datetime DEFAULT NULL COMMENT '访问时间',
`username` varchar(50) DEFAULT NULL COMMENT '操作者用户名',
`ip` varchar(40) DEFAULT NULL COMMENT '访问ip',
`url` varchar(40) DEFAULT NULL COMMENT '访问资源url',
`executionTime` int(11) DEFAULT NULL COMMENT '执行时长',
`className` varchar(255) DEFAULT NULL COMMENT '访问方法类名',
`methodName` varchar(255) DEFAULT NULL COMMENT '访问方法名',
`args` varchar(255) DEFAULT NULL COMMENT '访问方法参数',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=181 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

创建实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysLog {
private int id;
private Date visitTime;
private String visitTimeStr;
private String username;
private String ip;
private String url;
private Long executionTime;
private String className;
private String methodName;
private String args;

public String getVisitTimeStr() {
// 对日期格式化
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
if (null != visitTime) {
visitTimeStr = dateFormat.format(visitTime);
}
return visitTimeStr;
}
}

3.2 AOP日志处理

导入坐标

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>

修改applicationContext-tx.xml

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">


<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!--开启事务注解-->
<tx:annotation-driven/>

</beans>

修改springmvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">

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


<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/pages/"/>
<property name="suffix" value=".jsp"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>

<!-- 开启注解驱动 -->
<mvc:annotation-driven/>

<!-- 开启注解扫描包-->
<context:component-scan base-package="cn.yanqi.ssm.web" />

<!--开启AOP注解支持,开启AspectJ 注解自动代理机制,扫描含有@Aspect的bean-->
<aop:aspectj-autoproxy/>

</beans>

编写SysLogMapper

public interface SysLogMapper {
@Insert("insert into syslog (visitTime,username,ip,url,executionTime,classname,methodName,args) values(#{visitTime},#{username},#{ip},#{url},#{executionTime},#{className},#{methodName},#{args})")
public int insert(SysLog sysLog);
}

编写SysLogService

public interface SysLogService {

public int add(SysLog syslog);

}@Service("sysLogService")
public class SysLogServiceImpl implements SysLogService {

@Autowired
SysLogMapper sysLogMapper;
@Override
public int add(SysLog syslog) {
return sysLogMapper.insert(syslog);
}
}

创建切面类

在aop层创建AOP类

@Component
@Aspect
public class LogAop {
//创建一个访问日志的切面类,交给spring管理

@Autowired
private HttpServletRequest request;

@Autowired
private SysLogService sysLogService;

private Date visitTime; //开始时间

//前置通知 主要是获取开始时间,执行的类是哪一个,执行的是哪一个方法 JoinPoint程序执行过程中明确的点
@Before("execution(* com.yunhe.controller.*.*(..))")
public void doBefore(JoinPoint jp) throws NoSuchMethodException {
visitTime = new Date();//当前时间就是开始访问的时间
}

//后置通知
@After("execution(* com.yunhe.controller.*.*(..))")
public void doAfter(JoinPoint jp) throws Exception {
long time = new Date().getTime() - visitTime.getTime(); //获取访问的时长
String className = jp.getTarget().getClass().getSimpleName(); //具体要访问的类
String methodName = jp.getSignature().getName(); //获取访问的方法的名称
Object[] args = jp.getArgs();
String url = request.getRequestURI();
String ip = request.getRemoteAddr();
ip = ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
//获取当前操作的用户
SecurityContext context = SecurityContextHolder.getContext();//从上下文中获了当前登录的用户
User user = (User) context.getAuthentication().getPrincipal();
String username = user.getUsername();

//将日志相关信息封装到SysLog对象
SysLog sysLog = new SysLog();
sysLog.setExecutionTime(time); //执行时长
sysLog.setIp(ip);
sysLog.setClassName(className);
sysLog.setMethodName(methodName);
sysLog.setArgs(Arrays.toString(args));
sysLog.setUrl(url);
sysLog.setUsername(username);
sysLog.setVisitTime(visitTime);

//调用Service完成操作
int add = sysLogService.add(sysLog);
}
}

注意事项

1、要想在AOP类中使用request类获取用户主机的IP地址,我们需要在web.xml配置request的监听器

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

2、aop动态代理注解扫描书写在springmvc中

3、aop切面类存放在sop包中,如果书写在controoler可能出现问题

测试

3.3 查询所有日志

编写SysLogMapper

//根据用户名称与url模糊查询
public List<SysLog> selectByUsernameAndUrl(@Param("username") String username, @Param("url")String url);<?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.yunhe.mapper.SysLogMapper">

<select id="selectByUsernameAndUrl" resultType="com.yunhe.javabean.SysLog">
select * from syslog
<where>
<if test="username!='' and username!=null ">
and username like '%' #{username} '%'
</if>

<if test="url!='' and url!=null ">
and url like '%' #{url} '%'
</if>
</where>
order by id desc
</select>

</mapper>

编写SysLogService

public List<SysLog> findAll(String username,String url);@Override
public List<SysLog> findAll(String username, String url) {
return sysLogMapper.selectByUsernameAndUrl(username,url);
}

编写SysLogController

@Controller
@RequestMapping("/syslog")
public class SysLogController {
@Autowired
SysLogService sysLogService;

@RequestMapping("/findAll")
public String findAll(HttpServletRequest request, @RequestParam(value = "username",required = false,defaultValue = "") String username,@RequestParam(value = "url",required = false,defaultValue = "") String url,@RequestParam(value = "page",required = false,defaultValue = "1") int page, @RequestParam(value = "limit",required = false,defaultValue = "5")int limit){
PageHelper.startPage(page,limit);
List<SysLog> all = sysLogService.findAll(username, url);
PageInfo<SysLog> pageInfo=new PageInfo<>(all);

request.setAttribute("pageInfo",pageInfo);
request.setAttribute("username",username);
request.setAttribute("url",url);
return "/syslog/syslog-list";
}
}

测试

【文章原创作者:阿里云代理 http://www.558idc.com/aliyun.html处的文章,转载请说明出处】
网友评论