JWT是现在前后端分离用的比较多的验证授权方式
Maven引入
<!--安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--JSON封装-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.36</version>
</dependency>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
核心思路
需要配置WebSecurityConfigurerAdapter
在configuration中的configure方法加入自定义认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
http的configure
// 去掉 CSRF
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭token
.and()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
.anyRequest()
.access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证
.and()
.formLogin() //开启登录
.successHandler(authenticationSuccessHandler) // 登录成功
.failureHandler(authenticationFailureHandler) // 登录失败
.permitAll()
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
// 记住我
http.rememberMe().rememberMeParameter("remember-me")
.userDetailsService(userDetailsService).tokenValiditySeconds(300);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
需要编写一个JWT的Token生成Util
/**
* JWT工具类,使用RSA加密方式
*/
public class JwtTokenUtil {
private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jwt.jks"); // 寻找证书文件
private static PrivateKey privateKey = null;
private static PublicKey publicKey = null;
static { // 将证书文件里边的私钥公钥拿出来
try {
KeyStore keyStore = KeyStore.getInstance("JKS"); // java key store 固定常量
keyStore.load(inputStream, "123456".toCharArray());
privateKey = (PrivateKey) keyStore.getKey("jwt", "123456".toCharArray()); // jwt 为 命令生成整数文件时的别名
publicKey = keyStore.getCertificate("jwt").getPublicKey();
} catch (Exception e) {
e.printStackTrace();
}
}
public static String generateToken(String subject, int expirationSeconds, String salt) {
return Jwts.builder()
.setClaims(null)
.setSubject(subject)
.setExpiration(new Date(System.currentTimeMillis() + expirationSeconds * 1000))
// .signWith(SignatureAlgorithm.HS512, salt) // 不使用公钥私钥
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
}
public static String parseToken(String token, String salt) {
String subject = null;
try {
Claims claims = Jwts.parser()
// .setSigningKey(salt) // 不使用公钥私钥
.setSigningKey(publicKey)
.parseClaimsJws(token).getBody();
subject = claims.getSubject();
} catch (Exception e) {
}
return subject;
}
}
核心接口
** 自定义AccessDeniedHandler实现类 **
作用和说明:
用来解决认证过(认证过可能是认证成功,可能是认证失败,统一称为认证过)的用户访问无权限资源时的异常
Spring 默认的AccessDeniedHandler只有对页面的处理,而此处我们是Ajax请求
如果需要同时处理,可根据HTTPUtils.isAjaxRequest(request)来判断即可
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("300");
responseBody.setMsg("Need Authorities!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
** 自定义AuthenticationEntryPoint实现类**
作用和说明:用来解决匿名用户 访问无权限资源时的异常处理
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("000");
responseBody.setMsg("Need Authorities!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
** 自定义AuthenticationFailureHandler实现类
作用和说明:自定义 认证失败 处理器
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("400");
responseBody.setMsg("Login Failure!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
** 自定义AuthenticationSuccessHandler实现类 **
作用和说明:自定义认证成功处理器,并分配JWT的Token
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("200");
responseBody.setMsg("Login Success!");
SelfUserDetails userDetails = (SelfUserDetails) authentication.getPrincipal();
String jwtToken = JwtTokenUtil.generateToken(userDetails.getUsername(), 300, "_secret");
responseBody.setJwtToken(jwtToken);
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
** 自定义LogoutSuccessHandler实现类 **
作用和说明:成功退出的处理器
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("100");
responseBody.setMsg("Logout Success!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
需要些一个Filter来拦截header里面的Token
关键代码
String username = JwtTokenUtil.parseToken(authToken, "_secret");
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
** RBAC服务**
/**
* RBAC权限访问控制
*/
@Component("rbacauthorityservice")
public class RbacAuthorityService {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object userInfo = authentication.getPrincipal();
boolean hasPermission = false;
if (userInfo instanceof UserDetails) {
String username = ((UserDetails) userInfo).getUsername();
//获取资源
Set<String> urls = new HashSet();
urls.add("/common/**"); // 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!
Set set2 = new HashSet();
Set set3 = new HashSet();
AntPathMatcher antPathMatcher = new AntPathMatcher();
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
return hasPermission;
} else {
return false;
}
}
}