一、课程目标 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
将我们之前书写写死的角色认证与权限认证查询数据库的形式添加
@Overridepublic 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);@Overridepublic 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";
}
}