5. Spring Boot Security资源管理持久化的实现

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>&nbsp;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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值