前后端分离 SpringBoot + SpringSecurity + JWT + RBAC 实现用户无状态请求验证
发布日期:2021-06-30 16:51:06 浏览次数:2 分类:技术文章

本文共 17990 字,大约阅读时间需要 59 分钟。

一、前言

修改自前文,十分贴近公司开发的生产环境

RBAC(Role-Based Access Control,基于角色的访问控制)

二、代码

代码已经放在 github 上了:https://github.com/larger5/SpringBoot_SpringSecurity_JWT_RBAC.git

这里写图片描述

1.pom

org.springframework.boot
spring-boot-starter-security
com.alibaba
fastjson
1.2.36
io.jsonwebtoken
jjwt
0.9.0

2.AjaxResponseBody

package com.cun.security3.bean;import java.io.Serializable;public class AjaxResponseBody implements Serializable{
private String status; private String msg; private Object result; private String jwtToken; public String getStatus() {
return status; } public void setStatus(String status) {
this.status = status; } public String getMsg() {
return msg; } public void setMsg(String msg) {
this.msg = msg; } public Object getResult() {
return result; } public void setResult(Object result) {
this.result = result; } public String getJwtToken() {
return jwtToken; } public void setJwtToken(String jwtToken) {
this.jwtToken = jwtToken; }}

3.AjaxAccessDeniedHandler

package com.cun.security3.config;import com.alibaba.fastjson.JSON;import com.cun.security3.bean.AjaxResponseBody;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@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)); }}

4.AjaxAuthenticationEntryPoint

package com.cun.security3.config;import com.alibaba.fastjson.JSON;import com.cun.security3.bean.AjaxResponseBody;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AjaxAuthenticationEntryPoint implements 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)); }}

5.AjaxAuthenticationFailureHandler

package com.cun.security3.config;import com.alibaba.fastjson.JSON;import com.cun.security3.bean.AjaxResponseBody;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AjaxAuthenticationFailureHandler implements 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)); }}

6.AjaxAuthenticationSuccessHandler

package com.cun.security3.config;import com.alibaba.fastjson.JSON;import com.cun.security3.bean.AjaxResponseBody;import com.cun.security3.utils.JwtTokenUtil;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@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)); }}

7.AjaxLogoutSuccessHandler

package com.cun.security3.config;import com.alibaba.fastjson.JSON;import com.cun.security3.bean.AjaxResponseBody;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AjaxLogoutSuccessHandler implements 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)); }}

8.JwtAuthenticationTokenFilter

package com.cun.security3.config;import com.cun.security3.utils.JwtTokenUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.stereotype.Component;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired SelfUserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) {
final String authToken = authHeader.substring("Bearer ".length()); 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); } } } chain.doFilter(request, response); }}

9.RbacAuthorityService

package com.cun.security3.config;import org.springframework.security.core.Authentication;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import javax.servlet.http.HttpServletRequest;import java.util.HashSet;import java.util.Set;@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
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; } }}

10.SelfUserDetails

package com.cun.security3.config;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.io.Serializable;import java.util.Collection;import java.util.Set;/** *  ① 定义 user 对象 */public class SelfUserDetails implements UserDetails, Serializable {
private String username; private String password; private Set
authorities; @Override public Collection
getAuthorities() {
return this.authorities; } public void setAuthorities(Set
authorities) {
this.authorities = authorities; } @Override public String getPassword() {
// 最重点Ⅰ return this.password; } @Override public String getUsername() {
// 最重点Ⅱ return this.username; } public void setUsername(String username) {
this.username = username; } public void setPassword(String password) {
this.password = password; } @Override public boolean isAccountNonExpired() {
return true; } @Override public boolean isAccountNonLocked() {
return true; } @Override public boolean isCredentialsNonExpired() {
return true; } @Override public boolean isEnabled() {
return true; }}

11.SelfUserDetailsService

package com.cun.security3.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Component;import java.util.HashSet;import java.util.Set;@Componentpublic class SelfUserDetailsService implements UserDetailsService {
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//构建用户信息的逻辑(取数据库/LDAP等用户信息) SelfUserDetails userInfo = new SelfUserDetails(); userInfo.setUsername(username); userInfo.setPassword(new BCryptPasswordEncoder().encode("123")); Set authoritiesSet = new HashSet(); GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN"); authoritiesSet.add(authority); userInfo.setAuthorities(authoritiesSet); return userInfo; }}

12.SpringSecurityConf

package com.cun.security3.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configurationpublic class SpringSecurityConf extends WebSecurityConfigurerAdapter {
@Autowired AjaxAuthenticationEntryPoint authenticationEntryPoint; // 未登陆时返回 JSON 格式的数据给前端(否则为 html) @Autowired AjaxAuthenticationSuccessHandler authenticationSuccessHandler; // 登录成功返回的 JSON 格式数据给前端(否则为 html) @Autowired AjaxAuthenticationFailureHandler authenticationFailureHandler; // 登录失败返回的 JSON 格式数据给前端(否则为 html) @Autowired AjaxLogoutSuccessHandler logoutSuccessHandler; // 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html) @Autowired AjaxAccessDeniedHandler accessDeniedHandler; // 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面) @Autowired SelfUserDetailsService userDetailsService; // 自定义user @Autowired JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception {
// 去掉 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 }}

13.JwtTokenUtil

package com.cun.security3.utils;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.io.InputStream;import java.security.KeyStore;import java.security.PrivateKey;import java.security.PublicKey;import java.util.Date;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; }}

三、其他

不足 解决
没有注销 token 登陆 每次登陆,生成 token 放到 Redis 数据库里边,调用接口的时候,先查有没有这个 token,注销时把 token 删除

四、测试示例(2018/8/29更新)

由于网友对 jwt 的使用方式存在疑问,这里更新一下测试方法,

借助了 postman,每次请求都是 ajax 方式,注意 get/post 等类型

1.登录示例

①注意是 post 请求,请求 /login,存好返回给你的 jwtToken,以后每次请求都要带上它

这里写图片描述

2.访问内部示例

① 注意任何请求要带上 jwtToken,不像之前的基于 Session,一登录成功就完事。

② 若还是请求失败,看 RbacAuthorityService 是否开放了该 url
这里写图片描述

五、关于 Token(2019.9.13更新)

实际开发中,没有必要引入 jjwt,token 存于 Redis 即可(自带有效期),性能更好。

其实本文代码真的写得很差,详细设计见下面链接:

转载地址:https://larger5.blog.csdn.net/article/details/81063438 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:[Python爬虫] 使用 Beautiful Soup 4 快速爬取所需的网页信息
下一篇:前后端分离 SpringBoot + SpringSecurity 权限解决方案

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月07日 09时41分29秒