前后端分离使用SpringSecurity整合JWT返回JSON
- 环境:
springboot 2.6.0-SNAPSHOT
参考大佬博客:https://blog.csdn.net/mengxianglong123/article/details/112463172
- 本文没有什么是
JWT ,什么是SpringSecurity 不了解的自行学习,且本文不涉及OAuth2
配置
application.yaml
server:
port: 9090
spring:
datasource:
username: root
password: 990415
url: jdbc:mysql://127.0.0.1:3306/my_blog?serverTimezone=UTC&characterEncoding=utf-8&useUnicode=true
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
mybatis-plus:
type-aliases-package: top.jybill.demo.pojo
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
table-prefix: sm_
使用mybatis-plus 便于开发
SpringSecurityConfig 配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@ConditionalOnClass({
MyUserDetailsServiceImpl.class,
MyAccessDeniedHandler.class,
MyAuthenticationExceptionHandler.class,
MyAuthenticationSuccessHandler.class,
MyForwardAuthenticationFailureHandler.class,
MyJWTAuthenticationFilter.class
})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService myUserDetailsServiceImpl;
private final MyAccessDeniedHandler myAccessDeniedHandler;
private final MyAuthenticationExceptionHandler myAuthenticationExceptionHandler;
private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
private final MyForwardAuthenticationFailureHandler myForwardAuthenticationFailureHandler;
private final MyJWTAuthenticationFilter myJWTAuthenticationFilter;
@Autowired
public SpringSecurityConfig(UserDetailsService myUserDetailsServiceImpl,
MyAccessDeniedHandler myAccessDeniedHandler,
MyAuthenticationExceptionHandler myAuthenticationExceptionHandler,
MyAuthenticationSuccessHandler myAuthenticationSuccessHandler,
MyForwardAuthenticationFailureHandler myForwardAuthenticationFailureHandler,MyJWTAuthenticationFilter myJWTAuthenticationFilter) {
this.myUserDetailsServiceImpl = myUserDetailsServiceImpl;
this.myAccessDeniedHandler = myAccessDeniedHandler;
this.myAuthenticationExceptionHandler = myAuthenticationExceptionHandler;
this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler;
this.myForwardAuthenticationFailureHandler = myForwardAuthenticationFailureHandler;
this.myJWTAuthenticationFilter = myJWTAuthenticationFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.myUserDetailsServiceImpl).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login/**", "/logout/**").permitAll()
.antMatchers("/t/in").authenticated()
.antMatchers("/t/inRole").authenticated()
.antMatchers("/t/out").permitAll()
.and()
.formLogin()
.successHandler(this.myAuthenticationSuccessHandler)
.failureHandler(this.myForwardAuthenticationFailureHandler)
.and()
.exceptionHandling().accessDeniedHandler(this.myAccessDeniedHandler)
.authenticationEntryPoint(this.myAuthenticationExceptionHandler)
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(this.myJWTAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.headers().cacheControl()
;
}
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
}
自定义service 登陆类
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
private final UserMapper userMapper;
@Autowired
public MyUserDetailsServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<MyUser> myUserQueryWrapper = new QueryWrapper<>();
myUserQueryWrapper.eq("account", username);
MyUser myUsers = userMapper.selectOne(myUserQueryWrapper);
List<GrantedAuthority> grantedAuthorities = null;
if (myUsers.getAdmin()) {
grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
}
return new User(myUsers.getAccount(),
new BCryptPasswordEncoder().encode(myUsers.getPassword()),
"1".equals(myUsers.getStatus()),
"1".equals(myUsers.getStatus()),
"1".equals(myUsers.getStatus()),
"1".equals(myUsers.getStatus()),
grantedAuthorities
);
}
}
登陆前异常处理类:没有登陆想访问需要登陆的接口
@Component
public class MyAuthenticationExceptionHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
HashMap<String, Object> ret = new HashMap<>();
if (e instanceof InsufficientAuthenticationException) {
ret.put("code", HttpServletResponse.SC_FORBIDDEN);
ret.put("msg", e.getMessage());
response.getWriter().write(JSON.toJSONString(ret));
}
}
}
登陆成功处理类:登陆成功生成token并生成springsecurity 的安全上下文
@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
HashMap<String, Object> claims = new HashMap<>();
claims.put("username", authentication.getName());
List<String> role = new ArrayList<>();
for (GrantedAuthority g : authentication.getAuthorities()) {
role.add(g.getAuthority());
}
claims.put("grantedAuthority", role);
String token = JJWTUtil.createToken(claims);
SecurityContextHolder.getContext().setAuthentication(authentication);
HashMap<String, Object> ret = new HashMap<>();
ret.put("token", token);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().println(JSON.toJSONString(ret));
}
}
我们后续需要拿着返回的JSON - token 访问接口
登陆失败处理类:账号密码错误
@Component
public class MyForwardAuthenticationFailureHandler implements AuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8");
HashMap<String, Object> ret = new HashMap<>();
ret.put("code", 400);
ret.put("msg", "账号密码错误");
response.getWriter().println(JSON.toJSONString(ret));
}
}
JWT校验过滤器:校验token是否合法,是否过期
@Slf4j
@Component
public class MyJWTAuthenticationFilter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Authentication";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authentication = request.getHeader(TOKEN_PREFIX);
Claims claims = null;
if (StrUtil.isNotBlank(authentication)) {
claims = JJWTUtil.checkToken(authentication);
} else {
logger.warn("没有传JWT");
}
log.info("security上下文: {}", SecurityContextHolder.getContext().getAuthentication());
if (claims != null && SecurityContextHolder.getContext().getAuthentication() == null) {
String username = claims.get("username", String.class);
ArrayList<String> grantedAuthority = (ArrayList<String>)claims.get("grantedAuthority");
StringBuilder authorityString = new StringBuilder();
for (String s : grantedAuthority) {
authorityString.append(s);
authorityString.append(",");
}
String s = StrUtil.subBefore(authorityString.toString(), ",", true);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(s));
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
chain.doFilter(request, response);
}
}
token 合法、为失效会生成安全上下文,否则交给springsecurity 自己判断,肯定是失败的,就会走登陆后权限异常的处理器
权限异常处理器
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
HashMap<String, Object> map = new HashMap<>();
map.put("msg", accessDeniedException.getMessage());
map.put("code", 403);
System.out.println(map);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8");
response.getWriter().println(JSON.toJSONString(map));
}
}
controller + postman测试
@RestController
@RequestMapping("/t")
public class TestController {
@GetMapping("out")
public Map<String, Object> outSecurity() {
HashMap<String, Object> map = new HashMap<>();
map.put("msg", "out OK");
return map;
}
@GetMapping("in")
public Map<String, Object> inSecurity() {
HashMap<String, Object> map = new HashMap<>();
map.put("msg", "in OK");
return map;
}
@GetMapping("inAutority")
@PreAuthorize("hasAuthority('ban')")
public Map<String, Object> inAutority(Authentication authentication) {
HashMap<String, Object> map = new HashMap<>();
map.put("msg", "ban");
map.put("auth", authentication);
return map;
}
@GetMapping("notInAutority")
@PreAuthorize("hasAuthority('admin')")
public Map<String, Object> notInAutority(Authentication authentication) {
HashMap<String, Object> map = new HashMap<>();
map.put("msg", "admin");
map.put("auth", authentication);
return map;
}
}
jwt本身的特性就是无状态,所以没必要后端在db保存
源码
码云:https://gitee.com/JYbill/springseucirty-jjwt
|