IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 3.spring security授权流程 -> 正文阅读

[Java知识库]3.spring security授权流程

授权定义:

授权的方式包括 web授权和方法授权:

web授权是通过 url拦截进行授权,比如?

方法授权是通过 方法拦截进行授权。(在controller添加注解进行方法的授权)

他们都会调用accessDecisionManager进行授权决策:若为web授权则拦截器为FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。

如果同时通过web授权和方法授权则先执行web授权,再执行方法授权,最后决策通过,则允许访问资源,否则将禁止访问。

Spring Security的授权流程如下:

?分析授权流程:

1. 拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的 FilterSecurityInterceptor 的子类拦截。

2. 获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource 的子类 DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限 Collection 。 SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则, 读取访问策略如:

http
.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
...

3. 最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。

AccessDecisionManager(访问决策管理器)的核心接口如下:

public interface AccessDecisionManager {
/**
* 通过传递的参数来决定用户是否有访问对应受保护资源的权限
* configAttributes为要访问当前资源所需要的权限
*/
void decide(Authentication authentication , Object object, Collection<ConfigAttribute>
configAttributes ) throws AccessDeniedException, InsufficientAuthenticationException;
//略..
}

这里着重说明一下decide的参数:

authentication:要访问资源的访问者的身份

object:要访问的受保护资源,web请求对应FilterInvocation

configAttributes:是受保护资源的访问策略(要访问当前资源所需要的权限),通过SecurityMetadataSource获取。 decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。

decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。

准备环境:

角色表:

CREATE TABLE `t_role` ( `id` varchar(32) NOT NULL, `role_name` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, `status` char(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_role_name` (`role_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into `t_role`(`id`,`role_name`,`description`,`create_time`,`update_time`,`status`) values ('1','管理员',NULL,NULL,NULL,'');

用户角色关系表:

CREATE TABLE `t_user_role` ( `user_id` varchar(32) NOT NULL, `role_id` varchar(32) NOT NULL, `create_time` datetime DEFAULT NULL, `creator` varchar(255) DEFAULT NULL, PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into `t_user_role`(`user_id`,`role_id`,`create_time`,`creator`) values ('1','1',NULL,NULL);

权限表:

CREATE TABLE `t_permission` ( `id` varchar(32) NOT NULL, `code` varchar(32) NOT NULL COMMENT '权限标识符', `description` varchar(64) DEFAULT NULL COMMENT '描述', `url` varchar(128) DEFAULT NULL COMMENT '请求地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into `t_permission`(`id`,`code`,`description`,`url`) values ('1','p1','测试资源 1','/r/r1'),('2','p3','测试资源2','/r/r2');?

角色权限关系表:

CREATE TABLE `t_role_permission` ( `role_id` varchar(32) NOT NULL, `permission_id` varchar(32) NOT NULL, PRIMARY KEY (`role_id`,`permission_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into `t_role_permission`(`role_id`,`permission_id`) values ('1','1'),('1','2');?

新增UserDao中的方法:

package com.example.springsecurity.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springsecurity.entity.Permission;
import com.example.springsecurity.entity.User;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserDao extends BaseMapper<User>
{
    public User findUserByUserName(String userName);

    public List<Permission> findPermissionsByUserId(String userId);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.springsecurity.mapper.UserDao">

    <select id="findUserByUserName" parameterType="string" resultType="com.example.springsecurity.entity.User">
        select * from t_user where username = #{userName} limit 1
    </select>

    <select id="findPermissionsByUserId" parameterType="string" resultType="com.example.springsecurity.entity.Permission">
        SELECT * FROM t_permission where id in
            (
                select permission_id from t_role_permission where role_id in
                (select role_id from t_user_role where user_id = #{userId} )
            )
    </select>

</mapper>
UserDetails:
通过数据库中的userName查询该用户对应的权限,并把信息传给userDetails,让springsecurity通过判断前端传入的password(后端加密过后的)与userDetails的password进行比对,如果相等就进入首页index。
再将获得的userDetails的权限与index中的需要的权限进行比对,并判断是否显示。
package com.example.springsecurity.service;

import com.example.springsecurity.entity.Permission;
import com.example.springsecurity.mapper.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.springsecurity.entity.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.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException
    {
        User user = userDao.findUserByUserName(userName);
        if(user == null)
            return null;

        List<Permission> permissionList = userDao.findPermissionsByUserId(user.getId());
        //将permissionList转换成数组
        String[] permissionCodes = new String[permissionList.size()];
        for(int i=0;i<permissionCodes.length;i++)
        {
            permissionCodes[i] = permissionList.get(i).getCode();
        }
        return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()).authorities(permissionCodes).build();
    }
}

index:

            <!--菜单根据用户的角色动态的实现-->
            <!--登陆的角色权限有vip1,才显示level1组件-->
            <div class="column" sec:authorize="hasRole('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasAuthority('vip2')">
                <!--登陆的角色权限有vip2,才显示level2组件-->
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>l
l
            <div class="column" sec:authorize="hasRole('vip3')">
                <!--登陆的角色权限有vip3,才显示level3组件-->
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

web授权

在上面例子中我们完成了认证拦截,并对/level/**下的某些资源进行简单的授权保护,但是我们想进行灵活的授权控 制该怎么做呢?通过给 http.authorizeRequests() 添加多个子节点来定制需求到我们的URL,如下代码:

 //开启请求认证
        http.authorizeRequests() .antMatchers("/level1/login").permitAll()  //允许访问登录的请求
                .antMatchers("/level1/*").hasRole("vip1") // 访问请求为/level1需要有vip1的权限
                .antMatchers("/level1/*").hasAuthority("vip1")
                .antMatchers("/level2/*").hasRole("vip2")
                .antMatchers("/level2/*").hasAuthority("vip2")
                .antMatchers("/level3/*").hasRole("vip3")
                .antMatchers("/level3/*").hasAnyAuthority("vip1","vip3");

注意: 规则的顺序是重要的,更具体的规则应该先写.因此,登录的规则/level1/login应该在/level1/*规则之前,如果写在后面就不能进入登录页。

保护URL常用的方法有:

  • authenticated() 保护URL,需要用户登录
  • permitAll() 指定URL无需保护,一般应用与静态资源文件
  • hasRole(String role) 限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较.
  • hasAuthority(String authority) 限制单个权限访问
  • hasAnyRole(String… roles)允许多个角色访问.
  • hasAnyAuthority(String… authorities) 允许多个权限访问.
  • access(String attribute) 该方法使用 SpEL表达式, 所以可以创建复杂的限制.
  • hasIpAddress(String ipaddressExpression) 限制IP地址或子网

方法授权:

现在我们已经掌握了使用如何使用 http.authorizeRequests() 对web资源进行授权保护,从Spring Security2.0版 本开始,它支持服务层方法的安全性的支持。

本节学习@PreAuthorize,@PostAuthorize, @Secured(不常用)三类注解。

我们可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注释来启用基于注解的安全性。

开启Spring Security的 @prePost注解。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{}

在controller方法上添加权限认证:

 @RequestMapping("/level1/{id}")
    @PreAuthorize("isAnonymous()") //可以匿名访问
    public String level1(@PathVariable("id") int id)
    {
        return "views/level1/"+id;
    }

    @RequestMapping("/level2/{id}")
    @PreAuthorize("hasAnyAuthority('vip2','vip3')")  //拥有vip2或者vip3权限才可以访问
    public String level2(@PathVariable("id") int id)
    {
        return "views/level2/"+id;
    }

    @RequestMapping("/level3/{id}")
    @PreAuthorize("hasAuthority('vip3')") //拥有vip3权限才可以访问
    public String level3(@PathVariable("id") int id)
    {
        return "views/level3/"+id;
    }

?

?

?用lisi登录,只有vip1和vip2的权限,所以没有vip3的权限,不能显示level3页面,更不能显示level3对应哪个页面了

对应的逻辑也就是先登录=》通过自定义UserDetailsServiceImpl的loadUserByUsername方法获取对应的用户密码和权限=》spring security验证密码对不对,验证成功跳转到index页面=》index页面通过sec:authorize="hasAuthority('对应权限')"来判断是否显示该level=》

通过点击index的levelx-x跳转到对应的controller方法中,controller通过@PreAuthorize()判断是否该用户有权限调用该接口,有权限的话再跳转到对应页面。

?

<div class="column" sec:authorize="hasAuthority('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasAuthority('vip2')">
                <!--登陆的角色权限有vip2,才显示level2组件-->
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>l
l
            <div class="column" sec:authorize="hasAuthority('vip3')">
                <!--登陆的角色权限有vip3,才显示level3组件-->
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:14:49  更:2022-02-28 15:19:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:26:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码