Spring Security OAuth 匿名认证的使用
AnonymousAuthenticationFilter配置匿名访问的资源路径自定义匿名访问注解自定义注解AnonymousAccess动态获取匿名访问路径获取@AnonymousAccess标注的接口为空问题反射获取@AnonymousAccess标注的接口
AnonymousAuthenticationFilter
spring security 默认提供了一个匿名身份验证的过滤器AnonymousAuthenticationFilter,默认定义了未经身份验证的用户可以访问资源的anonymousUser身份验证Authentication 对象:
protected Authentication createAuthentication(HttpServletRequest request) {
// key 为一个UUID.randomUUID().toString()
// principal 为anonymousUser
// authorities是包含一个“ROLE_ANONYMOUS”的GrantedAuthority List集合
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
但前提是我们事先配置了哪些资源访问需要进行匿名访问,否则这个配置将不起作用。
配置匿名访问的资源路径
匿名地址访问配置在了资源服务(Resource Servers)中了。我的Authorization Server和Resource Servers在同一个项目上。
@EnableResourceServer
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
......
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 允许匿名访问的路径
.antMatchers("/controller/test").anonymous()
.anyRequest().authenticated().and().logout()
.and().csrf().disable();
}
}
/controller/test 为允许匿名访问的路径,其他必须经过身份验证才能进行资源访问。antMatchers(String… antPatterns)接收的是一个可变长度的参数,所以可以配置多个路径。
自定义匿名访问注解
这里目的是想在@Controller标注的类的接口方法上添加注解然后动态获取到哪些路径需要设置为可匿名访问。
自定义注解AnonymousAccess
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}
动态获取匿名访问路径
动态的获取哪些接口上配置了@AnonymousAccess 注解。
@EnableResourceServer
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
......
@Autowired
private ApplicationContext applicationContext;
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 允许匿名访问的路径
.antMatchers(getAnonymousUrls()).anonymous()
.anyRequest().authenticated().and().logout()
.and().csrf().disable();
}
private String[] getAnonymousUrls (){
//查找匿名标记URL
Map
applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Set
for (Map.Entry
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (anonymousAccess != null) {
anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
}
}
return anonymousUrls.toArray(new String[0]);
}
}
获取@AnonymousAccess标注的接口为空问题
这里存在一个问题,在项目启动时通过applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();无法获取到Controller中被@AnonymousAccess标注的接口路径。原因是我们的类用的@EnableResourceServer注解
@EnableResourceServer
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
......
}
@EnableResourceServer该注释创建了一个带有硬编码 Order(3) 说明它启动比较早,早于Spring MVC的初始化,并且applicationContext对应的上下文为当前资源服务器配置的一个上下文,默认applicationContext id为 项目名-authorization。所以加载不到Controller中被@AnonymousAccess标注的注解的接口。
反射获取@AnonymousAccess标注的接口
针对上面的问题这里通过反射的方式来获取匿名访问的接口,可根据自己的实际需求来增减代码,这只是其中的一种实现方式。
public class AnnotionUtils {
private final static Logger logger = LoggerFactory.getLogger(AnnotionUtils.class);
/** 扫描包名 */
public static final String PACKAGE = "cn.test.controller";
/**
* 扫描Controller匿名请求链接
*
* @return
* @throws Exception
*/
public static String[] scanControllerAnonymousUrls() throws Exception {
Reflections reflections = new Reflections(PACKAGE);
Set
List
for (Class> classes : classesList) {
{
String rootUrl = "";
boolean rootRequestMappings = classes.isAnnotationPresent(RequestMapping.class);
if (rootRequestMappings) {
// 获取 annotation 这个代理实例所持有的 InvocationHandler
InvocationHandler invocationHandler = Proxy
.getInvocationHandler(classes.getAnnotation(RequestMapping.class));
rootUrl = getClassOrMethodFormatUrl(invocationHandler);
if (!rootUrl.startsWith("/")) {
rootUrl = "/" + rootUrl;
}
}
Method[] methods = classes.getMethods();
if (null != methods) {
for (Method method : methods) {
// 判断是否方法上存在注解 AnonymousAccess
boolean annotationPresent = method.isAnnotationPresent(AnonymousAccess.class);
boolean requestMappings = method.isAnnotationPresent(RequestMapping.class);
if (annotationPresent && requestMappings) {
// 获取 annotation 这个代理实例所持有的 InvocationHandler
InvocationHandler invocationHandler = Proxy
.getInvocationHandler(method.getAnnotation(RequestMapping.class));
String methodUrl = getClassOrMethodFormatUrl(invocationHandler);
if (null != methodUrl) {
if (!rootUrl.endsWith("/") && !methodUrl.startsWith("/")) {
methodUrl = "/" + methodUrl;
methodUrl = rootUrl + methodUrl;
} else {
methodUrl = rootUrl + methodUrl;
}
listUrls.add(methodUrl);
}
}
}
}
}
}
String[] urls = listUrls.toArray(new String[0]);
logger.warn("匿名请求链接地址配置为 {}.", JSON.toJSONString(urls));
return urls;
}
/**
* 获取类或者方法上的配置路径
*
* @param invocationHandler
* @return
* @throws Exception
*/
private static String getClassOrMethodFormatUrl(InvocationHandler invocationHandler) throws Exception {
// 获取 AnnotationInvocationHandler 的 memberValues 字段
Field declaredField = invocationHandler.getClass().getDeclaredField("memberValues");
// 允许访问私有变量
declaredField.setAccessible(true);
// 获取 memberValues
@SuppressWarnings("unchecked")
Map
for (Entry
Object value = entry.getValue();
String key = entry.getKey();
if ("value".equals(key)) {
String[] values = (String[]) value;
return values[0];
}
}
return null;
}
}
然后调用AnnotionUtils.scanControllerAnonymousUrls()方法获取匿名访问路径。
@EnableResourceServer
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
......
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 允许匿名访问的路径
.antMatchers(AnnotionUtils.scanControllerAnonymousUrls()).anonymous()
.anyRequest().authenticated().and().logout()
.and().csrf().disable();
}
}
需要依赖的jar