SSO客户端设计
下面通过模块merchant-security对 SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。
安全认证的项目管理配置
SSO客户端安全认证的项目管理使用了如下所示的依赖配置:
代码语言:javascript复制<dependencies>
<dependency>
<groupId>com.demo</groupId>
<artifactId>merchant-client</artifactId><version>${project.version)</version></dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId></dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>
这个配置除主要引用Spring Cloud OAuth 2组件实现应用的安全管理和认证功能外,还引用了merchant-client模块以提供调用商家服务接口的功能,引用了Spring Boot Redis组件以提供使用缓存的功能。
安全认证项目的配置类
在SSO的客户端中启用Spring Security的认证功能,主要是通过一个配置类实现的。如下代码所示,我们创建一个配置类SecurityConfiguration,它继承于 WebSecurityConfigurerAdapter:
代码语言:javascript复制@Configuration
@EnableOAuth2Sso
@EnableConfigurationProperties (SecuritySettings.class)
public class SecurityConfiguration extends webSecurityConfigurerAdapter{
@Autowired
private AuthenticationManager authenticationManager;@Autowired
private SecuritySettings settings;
@Autowired
private RoleRestService roleRestService;@Autowired
private RedisCache redisCache;
@Bean(name = BeanIds.AUTHENT ICATION MANAGER)Goverride
public AuthenticationManager authenticationManagerBean() throws Excepti
return super.authenticationManagerBean();
)
@override
public void configure(HttpSecurity http) throws Exception
http.antMatcher("/**")
.authorizeRequests()
.antMatchers( "/login**").permitAll()
.antMatchers(settings.getPermitall()). permitAll()
.anyRequest()
.authenticated()
.and ().csrf().requireCsrfProtectionMatcher (csrfSecurityRequest
Matcher())
.csrfTokenRepository (csrfTokenRepository()) .and()
.addFilterAfter (csrfHeaderFilter(),CsrfFilter.class).logout()
.logoutUrl("/logout").permitAll()
.logoutSuccessUrl(settings.getLogoutsuccssurl()).and()
.exceptionHandling().accessDeniedPage (settings.getDeniedpage();
}
@Bean
public CustomFilterSecurityInterceptor customFilter() throws Exception t
CustomFilterSecurityInterceptor customFilter = new
CustomFilterSecurityInterceptor();
customFilter.setSecurityMetadataSource (securityMetadataSource());customFilter.setAccessDecisionManager(accessDecisionManager());
customFilter.setAuthenticationManager(authenticationManager);
return customFilter;
}
@Bean
public CustomAccessDecisionManager accessDecisionManager() {
return new CustomAccessDecisionManager();
}
@Bean
public CustomSecurityMetadataSource securitywetadataSource(){
return new CustomSecurityMetadataSource(roleRestService,redisCache) ;
}
}
这个配置类主要实现了以下功能:
(1)使用注解@EnableOAuth2Sso启用应用的SSO客户端功能。
(2)通过重写configure方法,来使用一些自定义的安全配置。其中,SecuritySettings是一个自定义的配置类,提供了成功退出时的链接配置、允许访问的链接配置和拒绝访问的链接配置等设置参数。
(3)在配置中通过一个自定义过滤器customFilter引入其他几个以custom开头的自定义设计,包括安全管理元数据和权限管理验证等设计。
权限管理验证设计
用户权限验证设计主要由两部分设计组成,分别为安全资源元数据管理和用户访问资源权限检查设计。
创建一个安全资源元数据管理类 CustomSecurityMetadataSource,实现安全资源元数据管理设计,代码如下所示:
代码语言:javascript复制public class CustomSecurityMetadataSource implementsFilterInvocationSecurityMetadatasource{
private static final Logger logger =
LoggerFactory.getLogger (CustomSecurityMetadataSource.class);
public static final String MERCHANT CENTER ROLES ALL="MERCHANT_ CENTER ROLES ALL";
private PathMatcher pathMatcher = new AntPathMatcher();
private RoleRestService roleRestService;private RedisCache rediscache;
coverride
public Collection<ConfigAttribute> getAllConfigAttributes()
return null;
public CustomSecurityMetadataSource (RoleRestService roleRestSeRedisCache redisCache) {
super();
this.roleRestService = roleRestService;this.redisCache = redisCache;
private List<RoleQ0> loadResourceWithRoles()i
String roles = roleRestService.findList();
List<RoleQ0> list = new Gson().fromJson (roles, new
TypeToken<List<RoleQ0>>() {]}.getType());
if(list !=null) {
redisCache.set (MERCHANT_CENTER_ROLES_ALL "LIST",list,
180);
}
return list;
}
@override
public Collection<configAttribute> getAttributes(0bject object)
throws IllegalArgumentException {
String url = ((FilterInvocation) object).getRequestUrl();
//logger.info("请求资源:
er.info("请求资源:"
url);
//先从缓存中取角色列表
0bject objects = redisCache.get (MERCHANT CENTER ROLES ALL "LIST");List<RoleQo> roleQoList = null;
if(CommonUtils.isNull(objects)) {
roleQoList = loadResourceWithRoles();/ /如果缓存不存在,则从API 中读取角
//色列表
]else{
roleQoList=(List<RoleQo>)objects;
}
Collection<ConfigAttribute> roles = new ArrayList<>();//有权限的角色列表
//检查每个角色的资源,如果与请求资源匹配,则加入角色列表。为后面权限检查提供依据
if(roleQoList !=null && roleQoList.size() >0){
for (RoleQo role0o :roleQoList) {//循环角色列表
List<ResourceQo> resourceQos = roleQo.getResources();if(resourceQos != null &&resourceQos.size() >0)
for (ResourceQo resourceQo :resourceQos){//循环资源列表
if(resourceQo .getUrl()!=null &&
pathMatcher.match (resourceQo. getUr1() "/**",url)){
ConfigAttribute attribute =new
SecurityConfig(roleQo.getName());
roles.add(attribute);
logger.debug("加入权限角色列表===角色资源:{,角色名称:
{}=-=", resourceQo.getUrl(),roleQo.getName ());
break;
}
}
}
}
}
return roles;
}
}
在这个设计中,主要通过重写 FilterInvocationSecurityMetadataSource 的 getAttributes方法,从用户访问的URL资源中,检查系统的角色列表中是否存在互相匹配的权限设置。
如果存在,
则将其存入一个安全元数据的角色列表中。这个列表将为后面的权限检查提供依据
从这里可以看出,对于一个资源,如果我们不指定哪个角色可以访问,则所有用户都可以访问。
因为资源的元数据管理使用了动态加载的方法,所以对用户的权限管理也能实现在线更新,同时,这里还借助了缓存技术提高元数据的访问性能。
有了安全资源的元数据管理,我们就可以对用户的行为进行实时权限检查了。
创建一个权限检查的实现类 CustomAccessDecisionManager,对一个用户是否有权限访问资源进行实时权限检查。这个类实现了AccessDecisionManager,代码如下所示:
代码语言:javascript复制public class CustomAccessDecisionManager implements AccessDecisionManager {
private static final Logger logger =
LoggerFactory.getLogger (CustomAccessDecisionManager.class);
@Override
public void decide (Authentication authentication,0bject object,
Collection<ConfigAttribute>configAttributes)
throws AccessDeniedException,InsufficientAuthenticationExceptionif(configAttributes == nul1){
return;
}
//从 CustomSecurityMetadataSource (getAttributes)中获取请求资源所需的角色集合Iterator<ConfigAttribute> iterator= configAttributes.iterator();
while (iterator.hasNext()){
ConfigAttribute configAttribute= iterator.next();//有权限访问资源的角色
String needRole = configAttribute.getAttribute();logger.debug("具有权限的角色:" needRole);
//在用户拥有的权限中检查是否有匹配的角色
for (GrantedAuthority ga : authentication.getAuthorities())1
if (needRole.equals(ga.getAuthority())) {
return;
}
}
}
//如果所有用户角色都不匹配,则用户没有权限
throw new AccessDeniedException("没有权限访问!");
}
}
在这个设计中,通过重写AccessDecisionManager的权限决断方法decide,将安全管理元数据中的角色与用户的角色进行比较,如果用户的角色与元数据的角色匹配,则说明用户有访问权限,否则用户没有访问权限。
客户端应用接入sso
有了SSO客户端的安全管理封装之后,对于一个需要接入SSO的Web应用,只需在应用的项目管理配置中增加对SSO客户端安全管理组件的引用,就可以使用SSO的功能了。
下面我们以商家管理应用模块merchant-web 为例进行说明,其他 Web UI应用可以参照这种方法接入SSO。在商家管理后台中,需要接入SSO的客户端应用有库存管理、订单管理、物流管理等,可以根据实际需要决定。
首先,在项目配置管理中引用SSO客户端安全管理的封装组件,代码如下所示:
代码语言:javascript复制<!--单点登录--><dependency>
<groupId>com.demo</groupId>
<artifactId>merchant-security</artifactId><version>${project.version)</version>
</dependency>
因为是在同一个项目工程中引用的,所以使用了项目的版本号。如果是其他应用引用的,则将上面的版本改为2.1-SNAPSHOT。
其次,在应用的配置文件 application.yml中使用如下所示设置:
代码语言:javascript复制spring:redis:
host: 127.0.0.1port: 6379
security:
oauth2:
client:
client-id: ssoclient
client-secret: ssosecret
access-token-uri: http://localhost:8000/oauth/token
user-authorization-uri: http://localhost:8000/oauth/authorizeresource:
token-info-uri:http://localhost:8000/oauth/check token
securityconfig:
logoutsuccssurl:/tosignoutpermitall:
- /test/大★
- /actuator/*大deniedpage: /deny
ssohome: http://localhost:8000/
其中,redis使用了本地的服务器,security.oauth2配置项是Spring Cloud OAuth2组件使用的一些配置参数。我们使用这些参数设置clientld和 clientSecret,即 SSO服务端设计配置类中设定的客户端ID和密钥。而 accessTokenUri和userAuthorizationUri分别用来指定获取令牌和进行认证的端点。
securityconfig配置项下面的几个设置,是由配置类SecuritySettings提供的几个自定义配置参数设定的。其中,ssohome为接入SSO的客户端应用提供了一个访问SSO首页的链接。
除上面这些配置外,对于接入了SSO的 Web应用,在数据编辑和管理方面还需要做一些调整,以保证数据的创建和编辑能够正常提交。另外,接入了SSO的应用还可以根据用户权限自动分配菜单。
有关跨站请求的相关设置
在使用Spring Security之后,必须在页面中增加跨站请求伪造防御的相关设置,才能在创建或编辑数据时正常提交表单,否则有关表单提交的请求,将会被拒绝访问。
首先统一在页面模板loyout.html的头部增加如下所示代码:
代码语言:javascript复制<meta name=" csrf" th:content="${csrf.token}"/>
<meta name=" csrf header" th:content="$ { csrf,headerName }"/>
然后在一个公共调用的 public.js中增加如下所示代码,以接收来自页面的传递参数:
$(function (){
var token = $("meta [name='_csrf']").attr( "content");
var header = $ ("meta[name='csrf header']").attr("content");$ (document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader (header, token);
});
});
这样做的目的是让后台能够验证页面表单提交的合法性,从而对数据提交起到一定的保护作用。
根据用户权限自动分配菜单
通过登录用户的关联对象,我们可以取得一个用户的菜单列表,从而可以根据用户权限自动分配用户的系统菜单。
在应用的主页控制器UserController 中使用如下所示的实现方法:
@Controller
@RequestMapping("/user")
public class UserController extends BaseController{
CRequestMapping("/index")
public String index (ModelMap model,Principal user, HttpServletRequestrequest) throws Exception {
List<ModelQo> menus = super.getModels(user.getName(), request);model.addAttribute("menus", menus);
model .addAttribute("user",user);
model .addAttribute("ssohome", ssohome);return "user/index";
}
}
其中,getModels就是获取用户菜单的具体实现,代码如下所示:
代码语言:javascript复制public abstract class BaseController {
private PathMatcher pathMatcher = new AntpathMatcher();
@Autowired
private UserRestService userService;
@value("${spring. application.name} ")private string serviceName;
public List<ModelQo> getModels (String userName,HttpServletRequestrequest){
//根据登录用户获取用户对象
String json = userService.findByName (userName) ;
UserQo user = new Gson().fromJson (json,UserQo.class);
//根据匹配分类获取模块(二级菜单)列表
List<ModelQo> modelList = new ArrayEist<>();List<Long>modelIds = new ArrayList<>();
for(RoleQo role : user.getRoles()){
for(ResourceQo resource : role.getResources()){
String link = resource.getModel().getKind ().getLink ();//分类顶级
//菜单链接
//获取模块列表,去重
if(! modelIds.contains (resource.getModel().getId())
& & pathMatcher.match(serviceName, link)){
modelList.add(resource. getModel ());
modelIds.add(resource.getModel().get Id());
}
}
}
return modelList;
}
}
首先从用户的角色中,找出其关联的资源列表;然后从每一个资源中找出与当前应用名称互相匹配的模块对象,最后经过去重整理之后得到的模块列表,就是一个应用的菜单体系。
通过使用这个模块列表,就可以在应用的导航页面nav.html上使用如下所示的设计,循环输出菜单:
代码语言:javascript复制<ul>
<li th:each="model:$ {menus}"><a th:classappend="${page ==
'$ {model.host}' ? 'currentPageNav': '')"th:href="e{${model.host}}"th: text="$ {model.name } "></a></li>
</ul>
通过上面这些设计,即可实现根据用户权限自动分配菜单的功能。完成上面的开发之后,就可以进行测试了。
首先确认注册中心已经启动,然后分别启动merchant-restapi应用、merchant-sso应用和merchant-web应用。
在所有应用启动成功之后,通过浏览器打开商家系统Web应用的链接:http://localhost:8081
打开链接后,在弹出的登录界面中输入前面单元测试中生成的用户名和密码进行登录。
登录成功后,即可打开商家系统merchant-web 应用的主页。
商家系统只有一个用户管理功能,所以它的主页如图10-8所示。在这里,商家管理员可以进行用户管理的操作。
小结
本章通过商豕伙版一知限管理,可以使用在分们个分散开发的微服务应用组信队台中,商家用户通过统一权限管理,可以使用在分布式环境中任何其他已经接
应用。商家管理后台设计以一种更加完善的方式,将各个分散开发的微服务应用组合成一个功能丰富的整体,充分体现了微服务架构设计的强大优势。
其中,商家权限体系设计,以访问资源为基础建立了三级菜单体系,并通过角色与资源的关系,将用户权限与菜单组成一个有机的整体。
商家的角色及其菜单的管理配置,必须由平台运营方进行操作。在下篇的平台管理后台的开发中,将实现管理商家的权限配置的功能。
本文给大家讲解的内容商家管理后台与sso设计:SSO客户端设计
- 下篇文章给大家讲解的是平台管理后台与商家菜单资源管理;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。