4.7.1 概述
授权的方式包括web授权和方法授权,web授权是通过url拦截进行授权,方法授权是通过方法拦截进行授权。他们都会调用accessDecisionManager进行授权决策,若为web授权则拦截器为FilterSecuritylnterceptor;若为方法授权则拦截器为Methodsecurityinterceptor。如果同时通过web授权和方法授权则先执行web授权,再执行方法授权。最后决策通过,则允许访问资源,否则将禁止访问。
4.7.2 准备环境
4.7.2.1 数据库环境
在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(l) 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' (
'userid' varchar(32) NOT NULL,
'roleid' varchar(32) NOT NULL,
'create_time' datetime DEFAULT NULL,
'creator' varchar(255) DEFAULT NULL,
PRIMARY KEY ('userid','role_id')
)ENGINE=InnoDB DEFAULT CHARSET=utf8
insert into 't_user_role'('user_id','roleid','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’,’ pl','测试资源 l','/r/rl'),('2','p3','测试资源2','/r/r2');
角色权限关系表:
CREATE TABLE 't_role_permission' (
'roleid' varchar(32) NOT NULL,
'permission_id' varchar(32) NOT NULL,
PRIMARY KEY ('role_id','permissionid')
)ENGINE=InnoDB DEFAULT CHARSET=utf8
insert into 't_role_permission'(' role_id','permission_id') values ('1','1')
4.7.2.2 修改 UserDetailService
1、修改dao接口
在UserDao中添加:
〃根据用户id查询用户权限
public List<String> findPermissionsByUserId(String userld){
String sql=...略
List<PermissionDto> list = jdbcTemplate.query(sql,new Object[]{
userid}new BeanPropertyRowMappero(PermissionDto.class));
List<String> permissions = new ArrayList<>();
list.iterator().forEachRemaining(c->permissions.add(c.getCode()));
return permissions;
2、修改UserDetailService
实现从数据库读取权限
@0verride
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录略
System.out.printin("username="+username);
//根据账号去数据库查询...
UserDto user = userDao.getUserByUsername(username);
if(user == null)(
return null;
}
//查询用户权限
List<String> permissions = userDao.findPermissionsByUserId(user.getId());
Stringf] perarray = new String[permissions.size()];
permissions.toArray(perarray);
//创建 userDetails
UserDetails userDetails =
User.withUsername(user.getFullname()).password(user.getPassword()).authorities(perarray)・ build();
return userDetails;
}
4.7.2 web授权
在上面例子中我们完成了认证拦截,并对/r/**下的某些资源进行简单的授权保护,但是我们想进行灵活的授权控制该怎么做呢?通过给http.authorizeRequests()添加多个子节点来定制需求 ,如下代码:
@Override
protected void configure(HttpSecurity http) throws Exception {
http .authorizeRequests() (1)
.antMatchers("/r/rl").hasAuthority("pl") (2)
.antMatchers("/r/r2").hasAuthority("p2") (3)
.antMatchers("/r/rS").access("hasAuthority('p1') and hasAuthority('p2')") (4)
.antMatchers("/r/**")]authenticated() (5)
.anyRequest().permitAll() ⑹
.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之外’7r/**''资源,同时通过身份认证就能够访问,这里使用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 ,需要用户登录 permitAII()指定URL无需保护,一般应用与静态资源文件
hasRole(String role)限制单个角色访问,角色将被增加"ROLE_".所以’ADMIN"将和"ROLE_ADMIN"进行比较.
hasAuthority(String authority)限制单个权限访问
hasAnyRole(String… roles)允许多个角色访问. hasAnyAuthority(String… authorities)允许多个权限访问.
access(String attribute)该方法使用SpEL表达式,所以可以创建复杂的限制.
haslpAddress(String ipaddressExpression)限制IP地址或子网
4.7.3 方法授权
现在我们已经掌握了使用如何使用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()
public Account readAccount(Long id);
@Secured()
public Accountf] findAccounts();
@Secured()
public Account post(Account account, double amount);
}
以上配置标明readAccount、findAccounts方法可匿名访问,底层使用WebExpressionVoter投票器,可从 AffirmativeBased 第23 行代码跟踪。 post方法需要有TELLER角色才能访问,底层使用RoleVoter投票器。
使用如下代码可启用prePost注解的支持
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
//...
}
相应Java代码如下:
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Accountf] findAccounts。;
@PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')") public Account post(Account account, double amount);
}