前后端分离 SpringBoot + SpringSecurity + JWT + RBAC 实现用户无状态请求验证
发布日期:2021-06-30 16:51:06
浏览次数:3
分类:技术文章
本文共 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(); //获取资源 Seturls = 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月12日 09时51分42秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Raft算法
2019-04-30
Python计算文本BLEU分数
2019-04-30
swap内存(linux)
2019-04-30
人脸au
2019-04-30
torch.distributed 分布式
2019-04-30
PyPy
2019-04-30
打印CSDN文章
2019-04-30
MATLAB与CUDA
2019-04-30
Linux png转jpg (convert命令)
2019-04-30
NAS (Network Attached Storage 网络附属存储)
2019-04-30
Ubuntu更新后终端中字体的颜色全是白色
2019-04-30
Ninja
2019-04-30
opencv相关操作(cv2) (python)
2019-04-30
Lua语言
2019-04-30
vscode git
2019-04-30
基于MATLAB的二进制数字调制与解调信号的仿真——2FSK
2019-04-30
基于MATLAB的二进制数字调制与解调信号的仿真——2PSK
2019-04-30
基于MATLAB的模拟调制信号与解调的仿真——AM
2019-04-30
基于MATLAB的模拟调制信号与解调的仿真——DSB
2019-04-30
基于MATLAB的模拟调制信号与解调的仿真——SSB
2019-04-30