Oauth2/密码流程/检查特定实体的权限
Oauth2 / Password flow / check permission for a specific entity
主要数据信息和我API中的信息链接到一个项目(Entity),
什么是密码流的好方法:管理链接到具有 spring 安全性和 OAuth2 的项目的特定权限?
在此应用程序中,您有 5 个微服务:
- UAA微服务:授权服务器
- 目录微服务
- 订购微服务
- 发票微服务
- 客户微服务
缩放权限:
每个用户可以拥有多个项目,每个项目可以拥有的权限:
- CAN_MANAGE_CATALOG
- CAN_VIEW_CATALOG
- CAN_MANAGE_ORDER
- CAN_VIEW_ORDER
- CAN_MANAGE_INVOICE
- CAN_VIEW_INVOICE
- ...
我有很多想法,但我不确定我是否有好的方法:
用例:我想保护端点:
http://catalog-service/{project_key}/catalogs
只有对项目{project_key}具有VIEW_CATALOG或MANAGE_CATALOG权限的USER才能列出项目
中存在的所有目录
我的第一个想法:使用带有预授权的 ProjectAccessExpression
CatalogController.java
@Controller
public class CatalogController {
@PreAuthorize("@projectAccessExpression.hasPermission(#projectKey, 'manageCatalog', principal)" +
" or @projectAccessExpression.hasPermission(#projectKey, 'viewCatalog', principal)")
@RequestMapping(
value = "/{projectKey}/catalogs",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE
)
public @ResponseBody List<Catalog> findByProject(@PathVariable("projectKey") String projectKey) {
return catalogService.find();
}
}
ProjectAccessExpression.java
@Component
public class ProjectAccessExpression {
private RestTemplate restTemplate;
public boolean havePermission(String projectKey, String permission , String username) {
Boolean havePermission = restTemplate.getForObject(String.format("http://uaa-service/permission/check?project=%1&permission=%2&username=%3",
projectKey, permission, username
), Boolean.class);
return havePermission;
}
}
不方便的是每次都需要调用UAA Service
第二个想法:使用USER_ROLE
与user_role
- 用户名 |角色
- 我的登录名1 | SHOP1.CAN_MANAGE_CATALOG
- 我的登录名1 | SHOP1.CAN_VIEW_CATALOG
- 我的登录名1 | SHOP2.CAN_MANAGE_CATALOG
- 我的登录名1 | SHOP2.CAN_VIEW_CATALOG
- 我的登录名1 | SHOP2.CAN_MANAGE_ORDER
- 我的登录名1 | SHOP2.CAN_VIEW_ORDER
- ...
SHOP1是SHOP2是projectKey
不方便:我不确定,但如果用户更改权限,我需要撤销所有令牌关联
第三个想法:在身份验证 blob 中添加特定权限
我不知道如何存储...
并在控制器中添加注释:
@PreAuthorize("@ProjectAccessExpression.hasPermission(authentication, 'manageCatalog||viewCatalog', #projectKey)
不方便:第二个想法同样不方便
基本上看起来您只是在尝试为您的项目利用 OAuth 2.0 的角色。这是关于 OAuth 2.0
的一些 spring 文档的摘录
将用户角色映射到范围:http://projects.spring.io/spring-security-oauth/docs/oauth2.html
有时不仅通过分配给客户端的范围,而且根据用户自己的权限来限制令牌的范围是有用的。如果您在 AuthorizationEndpoint 中使用 DefaultOAuth2RequestFactory,您可以设置标志 checkUserScopes=true 来限制允许的范围仅匹配用户角色的那些。您还可以将 OAuth2RequestFactory 注入 TokenEndpoint 但这只有在您还安装 TokenEndpointAuthenticationFilter 时才有效(即密码授权) - 您只需在 HTTP BasicAuthenticationFilter 之后添加该过滤器。当然,您也可以实现自己的规则以将范围映射到角色并安装您自己的 OAuth2RequestFactory 版本。 AuthorizationServerEndpointsConfigurer 允许您注入自定义 OAuth2RequestFactory,因此如果您使用 @EnableAuthorizationServer,您可以使用该功能来设置工厂.
所有这一切基本上归结为您可以通过将范围映射到您自己的自定义角色来保护具有不同范围的端点。这将使您能够真正细化安全性。
我找到了一个非常好的演练,您可以将其用作参考:(显然,您必须根据自己的用例配置设置)
https://raymondhlee.wordpress.com/2014/12/21/implementing-oauth2-with-spring-security/
这个解决方案我用过并且工作得很好
** 1 - 用户签名时加载业务逻辑安全性 **
此示例在数据库中找到具有角色的用户,并添加所有依赖于角色的项目。操作后我有身份验证令牌
GrantedAuthority:ROLE_USER、ROLE_MANAGE_CATALOG:project1、ROLE_VIEW_PROFILE:project1、ROLE_MANAGE_PROJECT:project2、...
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userService.findByLogin(username);
if (!user.isPresent()) {
Object args[] = {username};
throw new UsernameNotFoundException(
messageSource.getMessage("user.notexist", args, "User {0} doesn't not exist", LocaleContextHolder.getLocale())
);
}
if (!user.get().isActivated()) {
//throw new UserNotActivatedException(String.format("User %s was not activated!", username));
Object args[] = {username};
throw new UserNotActivatedException(
messageSource.getMessage("user.notactivated", args, "User {0} was not activated", LocaleContextHolder.getLocale()));
}
//Here implement your proper logic
//Add busness logic security Roles
// eg ROLE_MANAGE_PROJECT:{project_key}, ROLE_MANAGE_CATALOG:{project_key}
List<Role> bRoles = projectService.getRolesForUser(username)
user.get().getRoles().addAll(
bRoles
);
UserRepositoryUserDetails userDetails = new UserRepositoryUserDetails(user.get());
return userDetails;
}
}
** 2 使用预授权表达式检查安全性**
在此示例中,只有拥有此权限的用户才能执行此操作:
- ROLE_ADMIN 或
ROLE_MANAGE_PROJECT:{projectKey}
@PreAuthorize("@oauthUserAccess.hasPermission(认证, '"+Constants.PP_MANAGE_PROJECT+"', #projectKey)")
@RequestMapping(
值="/项目/{projectKey}",
方法 = RequestMethod.PUT,
产生 = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity updateProject(@PathVariable("projectKey") String projectKey,@Valid @RequestBody Project 项目)
OauthUserAccess class :
@Component("oauthUserAccess")
public class OauthUserAccess {
/**
* Check if it is the administrator of the application IMASTER
* @param authentication
* @param projectKey
* @return
*/
public boolean hasAdminPermission(OAuth2Authentication authentication, String projectKey) {
if(authentication.getOAuth2Request().getAuthorities().contains("ROLE_ADMIN")) return true;
return false;
}
/**
*
* @param authentication
* @param permissionType
* @param projectKey
* @return
*/
public boolean hasPermission(OAuth2Authentication authentication, String permissionType, String projectKey) {
if (!ProjectPermissionType.exist(permissionType) ||
projectKey.isEmpty() ||
!projectKey.matches(Constants.PROJECT_REGEX))
return false;
if (authentication.isClientOnly()) {
//TODO check scope permission
if(authentication.getOAuth2Request().getScope().contains(permissionType+":"+projectKey)) return true;
}
if (hasAdminPermission(authentication, projectKey)) return true;
String projectPermission = "ROLE_" + permissionType + ":" + projectKey;
String projectPermissionManage = "ROLE_" + permissionType.replace("VIEW", "MANAGE") + ":" + projectKey;
String manageProject = "ROLE_" + Constants.PP_MANAGE_PROJECT + ":" + projectKey;
Predicate<GrantedAuthority> p = r -> r.getAuthority().equals(projectPermission) || r.getAuthority().equals(projectPermissionManage) || r.getAuthority().equals(manageProject);
if (authentication.getAuthorities().stream().anyMatch(p)) {
return true;
};
return false;
}
}
3 - 优势/劣势
优势
业务逻辑权限仅在用户登录应用程序时加载,而不是每次都加载,因此它是微服务架构的强大解决方案。
劣势
需要更新身份验证令牌或在权限更改时撤销令牌
当您更新用户权限时,用户需要注销并登录。但是如果没有此安全逻辑,您会遇到同样的问题,例如当用户被禁用或启用时。
我在控制器中使用的解决方案:
newAuthorities = projectService.getRolesForUser(username);
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), newAuthorities);
OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
Collection<OAuth2AccessToken> accessTokens = tokenStore.findTokensByUserName(principal.getName());
OAuth2Authentication auth2 = new OAuth2Authentication(authentication.getOAuth2Request(), newAuth);
accessTokens.forEach(token -> {
if (!token.isExpired()) {
tokenStore.storeAccessToken(token, auth2);
}
});
主要数据信息和我API中的信息链接到一个项目(Entity), 什么是密码流的好方法:管理链接到具有 spring 安全性和 OAuth2 的项目的特定权限?
在此应用程序中,您有 5 个微服务:
- UAA微服务:授权服务器
- 目录微服务
- 订购微服务
- 发票微服务
- 客户微服务
缩放权限:
每个用户可以拥有多个项目,每个项目可以拥有的权限:
- CAN_MANAGE_CATALOG
- CAN_VIEW_CATALOG
- CAN_MANAGE_ORDER
- CAN_VIEW_ORDER
- CAN_MANAGE_INVOICE
- CAN_VIEW_INVOICE
- ...
我有很多想法,但我不确定我是否有好的方法:
用例:我想保护端点:
http://catalog-service/{project_key}/catalogs
只有对项目{project_key}具有VIEW_CATALOG或MANAGE_CATALOG权限的USER才能列出项目
中存在的所有目录我的第一个想法:使用带有预授权的 ProjectAccessExpression
CatalogController.java
@Controller
public class CatalogController {
@PreAuthorize("@projectAccessExpression.hasPermission(#projectKey, 'manageCatalog', principal)" +
" or @projectAccessExpression.hasPermission(#projectKey, 'viewCatalog', principal)")
@RequestMapping(
value = "/{projectKey}/catalogs",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE
)
public @ResponseBody List<Catalog> findByProject(@PathVariable("projectKey") String projectKey) {
return catalogService.find();
}
}
ProjectAccessExpression.java
@Component
public class ProjectAccessExpression {
private RestTemplate restTemplate;
public boolean havePermission(String projectKey, String permission , String username) {
Boolean havePermission = restTemplate.getForObject(String.format("http://uaa-service/permission/check?project=%1&permission=%2&username=%3",
projectKey, permission, username
), Boolean.class);
return havePermission;
}
}
不方便的是每次都需要调用UAA Service
第二个想法:使用USER_ROLE
与user_role
- 用户名 |角色
- 我的登录名1 | SHOP1.CAN_MANAGE_CATALOG
- 我的登录名1 | SHOP1.CAN_VIEW_CATALOG
- 我的登录名1 | SHOP2.CAN_MANAGE_CATALOG
- 我的登录名1 | SHOP2.CAN_VIEW_CATALOG
- 我的登录名1 | SHOP2.CAN_MANAGE_ORDER
- 我的登录名1 | SHOP2.CAN_VIEW_ORDER
- ...
SHOP1是SHOP2是projectKey
不方便:我不确定,但如果用户更改权限,我需要撤销所有令牌关联
第三个想法:在身份验证 blob 中添加特定权限
我不知道如何存储...
并在控制器中添加注释:
@PreAuthorize("@ProjectAccessExpression.hasPermission(authentication, 'manageCatalog||viewCatalog', #projectKey)
不方便:第二个想法同样不方便
基本上看起来您只是在尝试为您的项目利用 OAuth 2.0 的角色。这是关于 OAuth 2.0
的一些 spring 文档的摘录将用户角色映射到范围:http://projects.spring.io/spring-security-oauth/docs/oauth2.html
有时不仅通过分配给客户端的范围,而且根据用户自己的权限来限制令牌的范围是有用的。如果您在 AuthorizationEndpoint 中使用 DefaultOAuth2RequestFactory,您可以设置标志 checkUserScopes=true 来限制允许的范围仅匹配用户角色的那些。您还可以将 OAuth2RequestFactory 注入 TokenEndpoint 但这只有在您还安装 TokenEndpointAuthenticationFilter 时才有效(即密码授权) - 您只需在 HTTP BasicAuthenticationFilter 之后添加该过滤器。当然,您也可以实现自己的规则以将范围映射到角色并安装您自己的 OAuth2RequestFactory 版本。 AuthorizationServerEndpointsConfigurer 允许您注入自定义 OAuth2RequestFactory,因此如果您使用 @EnableAuthorizationServer,您可以使用该功能来设置工厂.
所有这一切基本上归结为您可以通过将范围映射到您自己的自定义角色来保护具有不同范围的端点。这将使您能够真正细化安全性。
我找到了一个非常好的演练,您可以将其用作参考:(显然,您必须根据自己的用例配置设置)
https://raymondhlee.wordpress.com/2014/12/21/implementing-oauth2-with-spring-security/
这个解决方案我用过并且工作得很好
** 1 - 用户签名时加载业务逻辑安全性 **
此示例在数据库中找到具有角色的用户,并添加所有依赖于角色的项目。操作后我有身份验证令牌 GrantedAuthority:ROLE_USER、ROLE_MANAGE_CATALOG:project1、ROLE_VIEW_PROFILE:project1、ROLE_MANAGE_PROJECT:project2、...
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userService.findByLogin(username);
if (!user.isPresent()) {
Object args[] = {username};
throw new UsernameNotFoundException(
messageSource.getMessage("user.notexist", args, "User {0} doesn't not exist", LocaleContextHolder.getLocale())
);
}
if (!user.get().isActivated()) {
//throw new UserNotActivatedException(String.format("User %s was not activated!", username));
Object args[] = {username};
throw new UserNotActivatedException(
messageSource.getMessage("user.notactivated", args, "User {0} was not activated", LocaleContextHolder.getLocale()));
}
//Here implement your proper logic
//Add busness logic security Roles
// eg ROLE_MANAGE_PROJECT:{project_key}, ROLE_MANAGE_CATALOG:{project_key}
List<Role> bRoles = projectService.getRolesForUser(username)
user.get().getRoles().addAll(
bRoles
);
UserRepositoryUserDetails userDetails = new UserRepositoryUserDetails(user.get());
return userDetails;
}
}
** 2 使用预授权表达式检查安全性**
在此示例中,只有拥有此权限的用户才能执行此操作:
- ROLE_ADMIN 或
ROLE_MANAGE_PROJECT:{projectKey}
@PreAuthorize("@oauthUserAccess.hasPermission(认证, '"+Constants.PP_MANAGE_PROJECT+"', #projectKey)") @RequestMapping( 值="/项目/{projectKey}", 方法 = RequestMethod.PUT, 产生 = MediaType.APPLICATION_JSON_VALUE ) public ResponseEntity updateProject(@PathVariable("projectKey") String projectKey,@Valid @RequestBody Project 项目)
OauthUserAccess class :
@Component("oauthUserAccess")
public class OauthUserAccess {
/**
* Check if it is the administrator of the application IMASTER
* @param authentication
* @param projectKey
* @return
*/
public boolean hasAdminPermission(OAuth2Authentication authentication, String projectKey) {
if(authentication.getOAuth2Request().getAuthorities().contains("ROLE_ADMIN")) return true;
return false;
}
/**
*
* @param authentication
* @param permissionType
* @param projectKey
* @return
*/
public boolean hasPermission(OAuth2Authentication authentication, String permissionType, String projectKey) {
if (!ProjectPermissionType.exist(permissionType) ||
projectKey.isEmpty() ||
!projectKey.matches(Constants.PROJECT_REGEX))
return false;
if (authentication.isClientOnly()) {
//TODO check scope permission
if(authentication.getOAuth2Request().getScope().contains(permissionType+":"+projectKey)) return true;
}
if (hasAdminPermission(authentication, projectKey)) return true;
String projectPermission = "ROLE_" + permissionType + ":" + projectKey;
String projectPermissionManage = "ROLE_" + permissionType.replace("VIEW", "MANAGE") + ":" + projectKey;
String manageProject = "ROLE_" + Constants.PP_MANAGE_PROJECT + ":" + projectKey;
Predicate<GrantedAuthority> p = r -> r.getAuthority().equals(projectPermission) || r.getAuthority().equals(projectPermissionManage) || r.getAuthority().equals(manageProject);
if (authentication.getAuthorities().stream().anyMatch(p)) {
return true;
};
return false;
}
}
3 - 优势/劣势
优势
业务逻辑权限仅在用户登录应用程序时加载,而不是每次都加载,因此它是微服务架构的强大解决方案。
劣势
需要更新身份验证令牌或在权限更改时撤销令牌 当您更新用户权限时,用户需要注销并登录。但是如果没有此安全逻辑,您会遇到同样的问题,例如当用户被禁用或启用时。
我在控制器中使用的解决方案:
newAuthorities = projectService.getRolesForUser(username);
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), newAuthorities);
OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
Collection<OAuth2AccessToken> accessTokens = tokenStore.findTokensByUserName(principal.getName());
OAuth2Authentication auth2 = new OAuth2Authentication(authentication.getOAuth2Request(), newAuth);
accessTokens.forEach(token -> {
if (!token.isExpired()) {
tokenStore.storeAccessToken(token, auth2);
}
});