1. 概述
本次给大家讲一下如何实现资源的持久化
2. 表机构以及数据
insert into sys_user_demo (user_id,user_name, user_passwd) values (1,'admin', 'admin');
insert into sys_user_demo (user_id,user_name, user_passwd) values (2,'abel', 'abel');
insert into sys_role_demo(role_id,role_name) values(1,'ROLE_ADMIN');
insert into sys_role_demo(role_id,role_name) values(2,'ROLE_USER');
insert into sys_role_demo(role_id,role_name) values(3,'ROLE_ANONYMOUS');
INSERT INTO sys_resource VALUES (1, 'ROLE_ADMIN', 'user', '/res/query', null);
INSERT INTO sys_resource VALUES (2, 'ROLE_USER', 'user', '/res/list', null);
INSERT INTO sys_resource VALUES (3, 'ROLE_ANONYMOUS', 'login', '/login', null);
insert into user_role_demo(user_role_id,role_id,user_id) values(1,1,1);
insert into user_role_demo(user_role_id,role_id,user_id) values(2,2,2);
INSERT INTO sys_resource_role VALUES (1, 1, 1);
INSERT INTO sys_resource_role VALUES (2, 1, 2);
INSERT INTO sys_resource_role VALUES (3, 2, 2);
INSERT INTO sys_resource_role VALUES (4, 3, 3);
3.接下来实现要添加的一些方法
UserDao.java
package com.hand.sys.dao;
import com.hand.sys.model.SysUserDemo;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* Created by JQY on 2017/12/3.
*/
@Repository
@Mapper
public interface UserDao
{
//用来查找角色
public List<Map<String,String>> findRole(String name);
}
SysResourceDao.java
package com.hand.sys.dao;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* Created by JQY on 2017/12/10.
*/
@Repository
@Mapper
public interface SysResourceDao
{
public List<Map<String,String>> findAll();
public List<Map<String,String>> findByUserId(int userId);
}
SysResourceMapper.xml
<?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.hand.sys.dao.SysResourceDao" >
<!--khm 是配置文件的别名,本体是KeyHashMap-->
<select id="findAll" resultType="khm">
SELECT * from SYS_RESOURCE
</select>
<select id="findByUserId" parameterType="int" resultType="khm">
select sr.*
from user_role_demo ur,sys_resource_role srr, sys_resource sr
where ur.role_id = srr.role_id
and srr.resource_id = sr.id
and ur.user_id =#{userId}
</select>
</mapper>
4.配置类
MyAccessDecisionManager.java 用于判断权限
package com.hand.security;
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;
/**
* Created by JQY on 2017/12/10.
*/
@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;
}
}
MyInvocationSecurityMetadataSourceService.java
package com.hand.security;
import com.hand.sys.dao.SysResourceDao;
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.*;
/**
* Created by JQY on 2017/12/10.
*/
@Service
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {
@Autowired
private SysResourceDao sysResourceDao;
private HashMap<String, Collection<ConfigAttribute>> map = null;
/**
* 加载权限表中所有权限
*/
public void loadResourceDefine() {
map = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
List<Map<String, String>> res = sysResourceDao.findAll();
for (Map<String, String> re : res) {
array = new ArrayList<>();
cfg = new SecurityConfig(re.get("name"));
//此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
array.add(cfg);
//用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
map.put(re.get("url"), 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;
}
}
MyFilterSecurityInterceptor.java
package com.hand.security;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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 java.io.IOException;
/**
* Created by JQY on 2017/12/10.
*/
@Service
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;
}
}
CustomUserService.java
package com.hand.security;
import com.hand.sys.dao.SysResourceDao;
import com.hand.sys.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by JQY on 2017/12/3.
*/
//@Service
public class CustomUserService implements UserDetailsService { //自定义UserDetailsService 接口
@Autowired
private UserDao userDao;
@Autowired
private SysResourceDao resourceDao;
//重写loadUserByUsername 方法获得 userdetails 类型用户
@Override
public UserDetails loadUserByUsername(String name) {
List<Map<String,String>> user = userDao.findRole(name);
if(user.get(0) == null){
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> authorities = new ArrayList <>();
//类型转换
Class<BigDecimal> type = BigDecimal.class;
BigDecimal userId = type.cast(user.get(0).get("userId"));
List<Map<String,String>> res = resourceDao.findByUserId(userId.intValue());
for (Map<String,String> re : res) {
if (re != null && re.get("name")!=null) {
//1:此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
authorities.add(new SimpleGrantedAuthority(re.get("name")));
}
}
return new User(user.get(0).get("userName"),
user.get(0).get("userPasswd"), authorities); //返回用户名密码, 来和前端传来的对比
}
}
同时WebSecurityConfig也要修改了
package com.hand.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* Created by JQY on 2017/11/21.
* 去除注解暂时废弃
*
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;//注入过滤器
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/","/styles/**","/scripts/**").permitAll() //指定那些URL不要被保护
.antMatchers("/login").permitAll()
//.antMatchers("/res/list").hasRole("USER") //ROLE_ 去除 一定要在 /** 前面才有作用
//.antMatchers("/**").hasRole("ADMIN") //此处要把 ROLE_ 去除
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") //登录的时候你要跳转到哪里
.failureUrl("/login?error") //失败页面
.permitAll() //登录任意访问
.and()
.rememberMe() //rememberMe/
.and() //注销行为任意访问
.logout()
.permitAll()
.and()
.csrf() //关闭csrf 不然不支持post
.disable();
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class); //添加过滤器
}
@Bean
UserDetailsService customUserService(){ //注册UserDetailsService 的bean
return new CustomUserService(); //注入刚刚写的service
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()); //user Details Service验证
/*auth
.inMemoryAuthentication()
//用户名密码和角色
.withUser("jyq").password("123456").roles("USER");*/
}
}
5. 最后写测试用的页面就好了
样式上一节有GIT地址
list_admin.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title>admin</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>欢迎角色</h1><h1 sec:authentication="name"></h1>
<h1>这里是admin_list</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</body>
</html>
list_user.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<title>hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>欢迎角色</h1>
<title sec:authentication="userName"></title>
<h1>这里是user_list</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</body>
</html>
resource_table.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Product list</h1>
<table>
<tr>
<th>资源名称</th>
<th>资源类型</th>
<th>资源大小</th>
</tr>
<tr th:each="re:${res}">
<td th:text="${re.sourceName}"></td>
<td th:text="${re.sourceType}"></td>
<td th:text="${re.sourceSize}"></td>
</tr>
</table>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="description" content="Appboard - Admin Template with Angularjs">
<meta name="keywords" content="appboard, webapp, admin, dashboard, template, ui">
<meta name="author" content="solutionportal">
<!-- <base href="/"> -->
<title>Appboard - Admin Template</title>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700,800' rel='stylesheet' type='text/css'/>
<!-- Icons -->
<link rel="stylesheet" type="text/css" href="http://www.jq22.com/jquery/font-awesome.4.6.0.css"/>
<!-- Set this in dist folder in index.html file -->
<link rel="stylesheet" type="text/css" href="http://www.jq22.com/jquery/bootstrap-3.3.4.css"/>
<link rel="stylesheet" th:href="@{{path}/styles/main.min.css(path=${contextPath})}"/>
<!-- Match Media polyfill for IE9 -->
<!--[if IE 9]><!-->
<script type="application/javascript" th:src="@{{path}/scripts/ie/matchMedia.js(path=${contextPath})}"></script>
<!--<![endif]-->
</head>
<body ng-app="app" id="app" class="app {{themeActive}}" custom-page ng-controller="AppCtrl">
<div class="page page-auth clearfix">
<div class="auth-container">
<!-- site logo -->
<h1 class="site-logo h2 mb15"><a href="/"><span>App</span> Board</a></h1>
<h3 class="text-normal h4 text-center">Sign in to your account</h3>
<div class="form-container">
<form class="form-horizontal" th:action="@{/login}" method="post">
<div class="form-group form-group-lg">
<input name="username" class="form-control" type="text" placeholder="用户名" value="">
</div>
<div class="form-group form-group-lg">
<input name="password" class="form-control" type="password" placeholder="密码">
</div>
<div class="clearfix"><a href="#/pages/forget-pass" class="right small">忘记密码</a>
</div>
<div class="clearfix mb15">
<button type="submit" class="btn btn-lg btn-w120 btn-primary text-uppercase">登录</button>
<div class="ui-checkbox ui-checkbox-primary mt15 right">
<label>
<input type="checkbox" id="keep-login" name="remember-me">
<span>Remember me</span>
</label>
</div>
</div>
<div class="clearfix text-center">
<p>Don't have an account? <a href="#/pages/register">Register Now</a>
</p>
</div>
</form>
<div th:if="${param.error}">
<span style="color: red">用户名或密码错误</span>
</div>
<div th:if="${param.logout}">
<span style="color: red">您已注销成功</span>
</div>
</div>
</div>
<!-- ##end auth-wrap -->
</div>
<!-- Set this in dist index.html -->
<script type="application/javascript" th:src="@{{path}/scripts/vendors.js(path=${contextPath})}"></script>
<script type="application/javascript" th:src="@{{path}/scripts/plugins.js(path=${contextPath})}"></script>
<script type="application/javascript" th:src="@{{path}/scripts/app.js(path=${contextPath})}"></script>
<!-- !End -->
</body>
</html>