上回书说到,认证服务器为了美观和良好的用户体验,修改了登录页面和返回登录失败的信息。本回就讲讲,认证客户端或者称之为资源服务器自定义权限认证和为移动端做的修改。
一般来说,像通过微信授权登录的系统都是有一套自己的权限体系的,微信那边只是拿到一个用户的信息。所以这里判断用户对应自己系统内部的权限需要开发者自行解决。
@Component
public class CustomPermission {
/**
* permission 获取接口设置的权限
*/
public boolean hasPermission(String permission) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(" >>> username : " + authentication.getPrincipal());
//TODO: 获取账号通过本系统数据库获取对应的权限进行逻辑处理
return true;
}
}
对应Controller使用
@PreAuthorize("@customPermission.hasPermission('SUPERADMIN')")
@RequestMapping("/byeuser")
public String byeUser() {
return "bye user!";
}
其次,返回错误信息,系统默认返回的都是英文单词和状态码,不接地气。那么还是要转化一下,至少看的更直白一点。
@Component
public class AuthExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authException) throws IOException, ServletException {
Throwable cause = authException.getCause();
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// CORS "pre-flight" request
httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.addHeader("Cache-Control", "no-cache");
httpServletResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with");
httpServletResponse.addHeader("Access-Control-Max-Age", "1800");
if (cause instanceof InvalidTokenException) {
//Token无效
JSONObject res = new JSONObject();
res.put("error", "ACCESS_TOKEN_INVALID");
res.put("error_description", "Token无效");
httpServletResponse.getWriter().write(res.toJSONString());
} else {
//资源未授权
JSONObject res = new JSONObject();
res.put("error", "UNAUTHORIZED");
res.put("error_description", "资源未授权");
httpServletResponse.getWriter().write(res.toJSONString());
}
}
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException accessException) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.addHeader("Cache-Control", "no-cache");
httpServletResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with");
httpServletResponse.addHeader("Access-Control-Max-Age", "1800");
//访问资源的用户权限不足
JSONObject res = new JSONObject();
res.put("error", "INSUFFICIENT_PERMISSIONS");
res.put("error_description", "访问资源的用户权限不足");
httpServletResponse.getWriter().write(res.toJSONString());
}
}
第三,使用Header的方式对App不是很友好,所以我们需要认可在URL后面拼接token的方式,emmm…本菜鸟一开始工作的时候做的App就是URL后面拼接token的。
@Component
@RequiredArgsConstructor
public class BearerTokenExtractor implements TokenExtractor {
@Override
public Authentication extract(HttpServletRequest request) {
String tokenValue = extractToken(request);
if (tokenValue != null) {
return new PreAuthenticatedAuthenticationToken(tokenValue, "");
}
return null;
}
protected String extractToken(HttpServletRequest request) {
String token = extractHeaderToken(request);
if (token == null) {
//从requestParameter中获取token
token = request.getParameter("token");
}
return token;
}
protected String extractHeaderToken(HttpServletRequest request) {
Enumeration<String> headers = request.getHeaders("Authorization");
while (headers.hasMoreElements()) {
//从Header中获取token
String token = headers.nextElement();
if (token != null && token.startsWith("Bearer ")) {
return token.substring(7);
}
}
return null;
}
}
第二和第三条使我们自定义的方法生效的代码如下:
@Override
public void configure(ResourceServerSecurityConfigurer endpoints) {
endpoints
.accessDeniedHandler(authExceptionHandler)
.authenticationEntryPoint(authExceptionHandler)
.tokenStore(tokenStore)
.tokenExtractor(tokenExtractor)
.resourceId("resource_id")
.tokenServices(tokenService());
}
DEMO:https://gitee.com/ichampion/Public-Project-Demo.git
至此,这一套系统基本可用了,本菜鸟还测试了一下统一登出,也是可以实现的。当然了,对于大量的系统接入,那么认证服务器是否要做集群化,session如何共享,这些都要在往后的工作中去探索。祝各位开发者小伙伴工作顺利!