security.springboot之授权

发布时间:2024年01月23日
授权的方式包括 web 授权和方法授权, web 授权是通过 url 拦截进行授权,方法授权是通过 方法拦截进行授权。他 们都会调用accessDecisionManager 进行授权决策,若为 web 授权则拦截器为 FilterSecurityInterceptor ;若为方法授权则拦截器为MethodSecurityInterceptor 。如果同时通过 web 授权和方法授权则先执行 web 授权,再执行方 法授权,最后决策通过,则允许访问资源,否则将禁止访问

数据库环境

t_user 数据库创建如下表:
角色表:
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');

修改UserDetailService

package com.itheima.security.springboot.dao;

import com.itheima.security.springboot.model.PermissionDto;
import com.itheima.security.springboot.model.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Administrator
 * @version 1.0
 **/
@Repository
public class UserDao {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //根据账号查询用户信息
    public UserDto getUserByUsername(String username){
        String sql = "select id,username,password,fullname,mobile from t_user where username = ?";
        //连接数据库查询用户
        List<UserDto> list = jdbcTemplate.query(sql, new Object[]{username}, new BeanPropertyRowMapper<>(UserDto.class));
        if(list !=null && list.size()==1){
            return list.get(0);
        }
        return null;
    }

    //根据用户id查询用户权限
    public List<String> findPermissionsByUserId(String userId){
        String sql = "SELECT * FROM t_permission WHERE id IN(\n" +
                "\n" +
                "SELECT permission_id FROM t_role_permission WHERE role_id IN(\n" +
                "  SELECT role_id FROM t_user_role WHERE user_id = ? \n" +
                ")\n" +
                ")\n";

        List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
        List<String> permissions = new ArrayList<>();
        list.forEach(c -> permissions.add(c.getCode()));
        return permissions;
    }
}

修改UserDetailService

package com.itheima.security.springboot.service;

import com.itheima.security.springboot.dao.UserDao;
import com.itheima.security.springboot.model.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
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.util.List;

/**
 * @author Administrator
 * @version 1.0
 **/
@Service
public class SpringDataUserDetailsService implements UserDetailsService {

    @Autowired
    UserDao userDao;

    //根据 账号查询用户信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //将来连接数据库根据账号查询用户信息
        UserDto userDto = userDao.getUserByUsername(username);
        if(userDto == null){
            //如果用户查不到,返回null,由provider来抛出异常
            return null;
        }
        //根据用户的id查询用户的权限
        List<String> permissions = userDao.findPermissionsByUserId(userDto.getId());
        //将permissions转成数组
        String[] permissionArray = new String[permissions.size()];
        permissions.toArray(permissionArray);
        UserDetails userDetails = User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(permissionArray).build();
        return userDetails;
    }
}

Web授权

在上面例子中我们完成了认证拦截,并对 /r/** 下的某些资源进行简单的授权保护,但是我们想进行灵活的授权控
制该怎么做呢?通过给 http.authorizeRequests() 添加多个子节点来定制需求到我们的 URL ,如下代码:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
    .authorizeRequests() (1)
    .antMatchers("/r/r1").hasAuthority("p1") (2)
    .antMatchers("/r/r2").hasAuthority("p2") (3)
    .antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')") (4)
    .antMatchers("/r/**").authenticated() (5)
    .anyRequest().permitAll() (6)
    .and()
    .formLogin()
    // ...
}
1 http.authorizeRequests() 方法有多个子节点,每个 macher 按照他们的声明顺序执行。
2 )指定 "/r/r1"URL ,拥有 p1 权限能够访问
3 )指定 "/r/r2"URL ,拥有 p2 权限能够访问
4 )指定了 "/r/r3"URL ,同时拥有 p1 p2 权限才能够访问
5 )指定了除了 r1 r2 r3 之外 "/r/**" 资源,同时通过身份认证就能够访问,这里使用 SpEL Spring Expression Language)表达式。。
6 )剩余的尚未匹配的资源,不做保护。
注意
规则的顺序是重要的 , 更具体的规则应该先写 . 现在以 / admin 开始的所有内容都需要具有 ADMIN 角色的身份验证用 户, 即使是 / admin / login 路径 ( 因为 / admin / login 已经被 / admin / ** 规则匹配 , 因此第二个规则被忽略 ).
(错误的写法)
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/admin/login").permitAll()
因此 , 登录页面的规则应该在 / admin / ** 规则之前 . 例如 .
(正确的)
.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
保护 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@Secured 注释

例如:
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {// ...}
然后向方法(在类或接口上)添加注解就会限制对该方法的访问。 Spring Security 的原生注释支持为该方法定义了 一组属性。 这些将被传递给AccessDecisionManager 以供它作出实际的决定:
public interface BankService {
    @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
    public Account readAccount(Long id);
    @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
    public Account[] findAccounts();
    @Secured("ROLE_TELLER")
    public Account post(Account account, double amount);
}
以上配置标明 readAccount findAccounts 方法可匿名访问,底层使用 WebExpressionVoter 投票器,可从 AffirmativeBased第 23 行代码跟踪。
post 方法需要有 TELLER 角色才能访问,底层使用 RoleVoter 投票器。

使用如下代码可启用prePost注解的支持?

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
public interface BankService {
    @PreAuthorize("isAnonymous()")
    public Account readAccount(Long id);
    @PreAuthorize("isAnonymous()")
    public Account[] findAccounts();
    @PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')")
    public Account post(Account account, double amount);
}
以上配置标明 readAccount findAccounts 方法可匿名访问, post 方法需要同时拥有 p_transfer p_read_account
权限才能访问,底层使用 WebExpressionVoter 投票器,可从 AffirmativeBased 23 行代码跟踪。
文章来源:https://blog.csdn.net/weixin_53535434/article/details/135776436
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。