SpringBoot+SpringSecurity+jwt实现前后端分离的权限认证(不用security的登陆和注销)

it2023-07-31  76

文章目录

涉及到的文件介绍AjaxAccessDeniedHandlerAjaxAuthenticationEntryPointJwtUtilsJwtAuthenticationTokenFilterUrlFilterInvocationSecurityMetadataSourceUrlAccessDecisionManagerSpringSecurityConfig

涉及到的文件介绍

AjaxAccessDeniedHandler----用户权限不足时反给前端的数据 AjaxAuthenticationEntryPoint----用户没登陆时反给前端的数据 JwtAuthenticationTokenFilter----Jwt过滤器(第一个过滤器):获取用户token,查询用户信息拼装到security中,以便后续filter使用 JwtUtils----Jwt工具包 SpringSecurityConfig----Security核心配置文件 UrlAccessDecisionManager----自定义的比较逻辑;根据用户信息和权限去与当前访问的url需要的权限进行对比 UrlFilterInvocationSecurityMetadataSource----这个类是分析得出 用户访问的 url 需要哪些权限 其它关于权限模块的表和bean类我就不记了,用脚都能写出来。 下面是根据用户Id获取用户权限的sql:

SELECT a.permission FROM sys_resource a WHERE a.id in ( SELECT resource_id FROM sys_role_resource WHERE role_id in ( SELECT role_id FROM sys_user_role WHERE user_id = #{userId} ) )

下面是具体实现代码,无冗余代码,JwtAuthenticationTokenFilter中第40行的方法就是上面那条SQL

AjaxAccessDeniedHandler

import com.alibaba.fastjson.JSON; import com.sonice1024.hongchen.common.response.ResponseCode; import com.sonice1024.hongchen.common.response.ResponseResult; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 用户权限不足时反给前端的数据 */ @Component public class AjaxAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException { ResponseResult result = ResponseResult.build(ResponseCode.NO_AUTHORITY); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setHeader("Content-Type", "application/json"); httpServletResponse.getWriter().write(JSON.toJSONString(result)); } }

AjaxAuthenticationEntryPoint

import com.alibaba.fastjson.JSON; import com.sonice1024.hongchen.common.response.ResponseCode; import com.sonice1024.hongchen.common.response.ResponseResult; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 用户没登陆时反给前端的数据 */ @Component public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException { ResponseResult result = ResponseResult.build(ResponseCode.TOKEN_FAIL); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setHeader("Content-Type", "application/json"); httpServletResponse.getWriter().write(JSON.toJSONString(result)); } }

JwtUtils

import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.commons.lang3.StringUtils; import java.util.Date; /** * Jwt工具包 */ public class JwtUtils { private static final String SUBJECT = "***"; private static final long EXPIRE = 1000*60*60*24*3; //过期时间,毫秒,天 // 秘钥 private static final String APPSECRET = "***"; /** * 生成jwt的token串 * @param username 用户名 * @return String token字符串 */ public static String createJwtToken(String username){ if(StringUtils.isBlank(username)){ return null; } String token = Jwts.builder().setSubject(SUBJECT) .claim("username", username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis()+EXPIRE)) .signWith(SignatureAlgorithm.HS512,APPSECRET) .compact(); return token; } /** * 从token中获取用户名 * @param token token字符串 * @return Steing 用户名 */ public static String getUsername(String token){ try { // 他自己会判断过期时间 final Claims claims = Jwts.parser().setSigningKey(APPSECRET).parseClaimsJws(token).getBody(); if (claims != null) return (String) claims.get("username"); } catch (Exception e) { return null; } return null; } }

JwtAuthenticationTokenFilter

import com.sonice1024.hongchen.entity.SysUser; import com.sonice1024.hongchen.service.SysUserService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; 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; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * Jwt过滤器(第一个过滤器):获取用户token,查询用户信息拼装到security中,以便后续filter使用 */ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private SysUserService sysUserService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 获取token String tokenStr = request.getHeader("Authorization"); if (StringUtils.isNotBlank(tokenStr)) { // 获取用户名 String tokenObj = JwtUtils.getUsername(tokenStr); if (StringUtils.isNotBlank(tokenObj)) { // 查询用户信息和权限列表 SysUser user = sysUserService.getAUserAndPermission(tokenObj); if (user != null) { // 将权限列表给security,以便后续filter使用 List<SimpleGrantedAuthority> authorities = new ArrayList<>(); if (user.getPermissions() != null && user.getPermissions().size() > 0) { authorities = user.getPermissions().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } //设置当前上下文的认证信息 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(tokenObj, "", authorities); SecurityContextHolder.getContext().setAuthentication(authentication); } } } //调用下一个过滤器 chain.doFilter(request, response); } }

UrlFilterInvocationSecurityMetadataSource

import com.sonice1024.hongchen.entity.SysResource; import com.sonice1024.hongchen.service.SysResourceService; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Collection; /** * URL过滤器(第二个过滤器): * 1. 这个类是分析得出 用户访问的 url 需要哪些权限 * 2. 核心的方法是第一个 * 3. 第三个方法返回true表示支持支持这种方式即可 */ @Component public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Resource private SysResourceService sysResourceService; /** * 在用户发出请求时,根据请求的url查出该url需要哪些权限才能访问,并将所需权限给SecurityConfig */ @Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { // 获取 请求 url 地址 String requestUrl = ((FilterInvocation) o).getRequestUrl(); // 得到所请求的 url 和 资源权限 的对应关系(这里可以用缓存处理) SysResource resource = sysResourceService.getPermissionByUrl(requestUrl); if (resource != null && resource.getVerification() == 1) { return SecurityConfig.createList(resource.getPermission()); } else { // 如果都没有匹配上,我们返回默认值,这个值就像一个特殊的标识符,自定义,在UrlAccessDecisionManager中自定义规则即可 return SecurityConfig.createList("ROLE_ANONYMOUS"); } } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return true; } }

UrlAccessDecisionManager

import org.springframework.http.HttpMethod; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterInvocation; import org.springframework.stereotype.Component; import java.util.Collection; /** * 根据用户信息和权限去与当前访问的url需要的权限进行对比 */ @Component public class UrlAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { //放行options请求 FilterInvocation fi = (FilterInvocation) o; if (HttpMethod.OPTIONS.name().equals(fi.getRequest().getMethod())) { return; } // collection是url过滤器给过来的权限列表,判断url是否需要拦截 for (ConfigAttribute attribute : collection) { if (!"ROLE_ANONYMOUS".equals(attribute.toString())) { // 需要拦截的 // 看是否有用户信息 Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal == null) throw new AccessDeniedException("expire"); // 看权限是否足够 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { // authority.getAuthority()是jwt过滤器给过来的权限 // attribute.getAttribute()是url过滤器给过来的权限 if (authority.getAuthority().equals(attribute.getAttribute())) { return; } } throw new AccessDeniedException("not allow"); } } } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }

SpringSecurityConfig

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.ObjectPostProcessor; 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.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired AjaxAuthenticationEntryPoint authenticationEntryPoint; // 未登陆时返回 JSON 格式的数据给前端(否则为 html) @Autowired AjaxAccessDeniedHandler accessDeniedHandler; // 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面) @Autowired private JwtAuthenticationTokenFilter jwtRequestFilter; // Jwt拦截器,获取用户的数据和用户拥有的权限 @Autowired UrlAccessDecisionManager urlAccessDecisionManager; // 自定义的权限比较器 @Autowired UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource; // 获取访问URL需要的权限 /** * 配置说明: * 1。 因为整个系统是前后端分离的,不需要Security自带的登陆和退出登陆功能,所以不需要配置.formLogin()和.logout(); * 如果我们使用session的方式记录用户登陆状态就需要在“登陆接口”将session保存到内存中,后面的授权Security会自己去拿session; * 如果用token方式,在“登陆接口”我们需要生成token给客户端,客户端在登陆后的其它请求中需要将token放在header中传入接口; * 默认是使用session保存用户登陆的状态的,我们需要改为使用token的模式: * 1. 用户访问接口,首先进入JwtAuthenticationTokenFilter,我们需要在这里面构建用户信息,包括用户有哪些权限 * 2. 经过JwtAuthenticationTokenFilter,得到用户有哪些权限后,进入UrlFilterInvocationSecurityMetadataSource获取用户 * 访问的url是否需要拦截 * 3. 所有的数据都有了,在UrlAccessDecisionManager中自定义比较即可 */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()//不使用防跨站攻击 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//不使用session .and().authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONS放行 .anyRequest().authenticated() // 其它的全部自定义验证 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setAccessDecisionManager(urlAccessDecisionManager); o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource); return o; } }); http.headers().cacheControl();//http的cache控制,如下这句代码会禁用cache http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);//添加JWT身份认证的filter //添加自定义未授权的处理器 http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); //添加自定义未登录的处理器 http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); } }

有兴趣交流的可以关注公众号或加我的QQ群:

最新回复(0)