授权的方?p>
桨?
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( " + " " + "SELECT permission_id FROM t_role_permission WHERE role_id IN( " + " SELECT role_id FROM t_user_role WHERE user_id = ? " + ") " + ") "; 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
行代码跟踪。