前后端分离通过SpringSecurity+JWT实现权限控制

    技术2026-08-14  8

    SpringSecurity+JWT

    我丢到了一个包下没有细分,请勿模仿

    数据库表

    application.yml

    jwt: secret: secret expiration: 7200000 token: Authorization

    SecurityUserDetails

    这里继承了我自己的实体类

    package com.experiment.blog.security; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.experiment.blog.mapper.UserBlogMapper; import com.experiment.blog.pojo.DTO.UserUpload; import com.experiment.blog.pojo.UserBlog; import com.experiment.blog.service.UserBlogService; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public class SecurityUserDetails extends UserBlog implements UserDetails { private Collection<? extends GrantedAuthority> authorities; public SecurityUserDetails(String userName, String password, Collection<? extends GrantedAuthority> authorities){ this.authorities = authorities; this.setUsername(userName); this.setPassword(password); this.setAuthorities(authorities); } /** * 账户是否过期 * @return */ @Override public boolean isAccountNonExpired() { return true; } /** * 是否禁用 * @return */ @Override public boolean isAccountNonLocked() { return true; } /** * 密码是否过期 * @return */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 是否启用 * @return */ @Override public boolean isEnabled() { return true; } }

    UserDetailsService

    这里RoleBlog实体类要实现GrantedAuthority接口,实现它的getAuthority方法,返回权限名roleName

    package com.experiment.blog.security; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.experiment.blog.exception.MyDefineException; import com.experiment.blog.pojo.RoleBlog; import com.experiment.blog.pojo.UserBlog; import com.experiment.blog.service.*; 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.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class JwtUserDetailsService implements UserDetailsService { @Autowired UserBlogService userBlogService; @Autowired RoleBlogService roleBlogService; @Autowired RoleUserBlogService roleUserBlogService; @Override /** * 这里通过UserName从数据库里取出权限和密码 */ public UserDetails loadUserByUsername (String userName) throws UsernameNotFoundException { System.out.println("JwtUserDetailsService:" + userName); Long userId = userBlogService.getIdByUserName(userName); if (null==userId){ throw new UsernameNotFoundException("用户名不存在"); } List<Long> roleIds = roleUserBlogService.getRoleIdByUserId(userId); String password = userBlogService.getOne(new QueryWrapper<UserBlog>().eq("username",userName)).getPassword(); return new SecurityUserDetails(userName,password,roleBlogService.list(new QueryWrapper<RoleBlog>().in("role_id",roleIds))); } }

    JWT模块

    JwtTokenUtil

    package com.experiment.blog.security; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Clock; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.DefaultClock; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = -3301605591108950415L; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; @Value("${jwt.token}") private String tokenHeader; private Clock clock = DefaultClock.INSTANCE; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put(tokenHeader,userDetails.getUsername()); claims.put("created",new Date()); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { final Date createdDate = clock.now(); final Date expirationDate = calculateExpirationDate(createdDate); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(createdDate) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } private Date calculateExpirationDate(Date createdDate) { return new Date(createdDate.getTime() + expiration); } public Boolean validateToken(String token, UserDetails userDetails) { SecurityUserDetails user = (SecurityUserDetails) userDetails; final String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token) ); } public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(clock.now()); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } }

    JwtAuthorizationTokenFilter

    package com.experiment.blog.security; import io.jsonwebtoken.ExpiredJwtException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; 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; @Component public class JwtAuthorizationTokenFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; private final JwtTokenUtil jwtTokenUtil; private final String tokenHeader; public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.token}") String tokenHeader) { this.userDetailsService = userDetailsService; this.jwtTokenUtil = jwtTokenUtil; this.tokenHeader = tokenHeader; } @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestHeader = request.getHeader(this.tokenHeader); String username = null; String authToken = null; if (requestHeader != null && requestHeader.startsWith("Bearer ")) { authToken = requestHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (ExpiredJwtException e) { } } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } }

    JwtAuthenticationEntryPoint

    没有凭证时的处理

    package com.experiment.blog.security; import com.alibaba.fastjson.JSON; import com.experiment.blog.common.CommonResult; 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; import java.io.PrintWriter; @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { //没有凭证时走这里 @Override public void commence (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { System.out.println("JwtAuthenticationEntryPoint:"+e.getMessage()); returnFailure(httpServletResponse); } public void returnFailure(HttpServletResponse response) throws IOException{ response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); CommonResult commonResult = new CommonResult<>(403,"没有凭证"); writer.write(JSON.toJSONString(commonResult)); writer.flush(); } }

    获取传入的用户名密码

    UsernamePasswordFilter

    package com.experiment.blog.security; import com.experiment.blog.exception.MyDefineException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; public class UsernamePasswordFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if(MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())){ String username=null; String password=null; try{ // Map<String,String > map = new ObjectMapper().readValue(request.getInputStream(), Map.class); username =request.getParameter("username"); password=request.getParameter("password"); }catch (Exception e){ throw new MyDefineException(405,"传入账号密码出错"); } if(username==null){ throw new MyDefineException(405,"用户名为空");}; if(password==null){throw new MyDefineException(405,"密码为空");} username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } return super.attemptAuthentication(request, response); } }

    验证访问权限

    MyInvocationSecurityMetadataSourceService

    package com.experiment.blog.security; import com.experiment.blog.pojo.DTO.RolePermission; import com.experiment.blog.service.PermissionBlogService; import com.experiment.blog.service.RolePermissionBlogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.*; @Component public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { @Autowired PermissionBlogService permissionBlogService; @Autowired RolePermissionBlogService rolePermissionBlogService; private static HashMap<String,Collection<ConfigAttribute>> map = null; @Override public Collection<ConfigAttribute> getAttributes (Object o) throws IllegalArgumentException { if (null == map){ loadResourceDefine(); } HttpServletRequest request = ((FilterInvocation)o).getHttpRequest(); for (Iterator<String> it = map.keySet().iterator();it.hasNext();){ String url = it.next(); if (new AntPathRequestMatcher(url).matches(request)){ return map.get(url); } } return null; } /** * 初始化资源 */ public void loadResourceDefine(){ map=new HashMap<>(16); List<RolePermission> rolePermissions = rolePermissionBlogService.getRolePermission(); rolePermissions.forEach(rolePermission -> { String url = rolePermission.getPermissionUrl(); ConfigAttribute role = new SecurityConfig(rolePermission.getRoleName()); if (map.containsKey(url)){ map.get(url).add(role); } else { List<ConfigAttribute> list = new ArrayList<>(); list.add(role); map.put(url,list); } }); } @Override public Collection<ConfigAttribute> getAllConfigAttributes () { return null; } @Override public boolean supports (Class<?> aClass) { return true; } }

    MyAccessDecisionManager

    package com.experiment.blog.security; import org.mybatis.logging.Logger; import org.mybatis.logging.LoggerFactory; 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.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; import java.io.PrintWriter; import java.util.Collection; import java.util.Iterator; @Component public class MyAccessDecisionManager implements AccessDecisionManager { private final static Logger logger = LoggerFactory.getLogger(MyAccessDecisionManager.class); /** * 通过传递的参数来决定用户是否有访问对应受保护对象的权限 * * @param authentication 包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。 * @param o 就是FilterInvocation对象,可以得到request等web资源 * @param collection configAttributes是本次访问需要的权限 */ @Override public void decide (Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { if(null == collection || 0>=collection.size()){ return; } else { String needRole; for(Iterator<ConfigAttribute> iter = collection.iterator(); iter.hasNext(); ) { needRole = iter.next().getAttribute(); for(GrantedAuthority ga : authentication.getAuthorities()) { if(needRole.trim().equals(ga.getAuthority().trim())) { return; } } } throw new AccessDeniedException("当前访问没有权限"); } } @Override public boolean supports (ConfigAttribute configAttribute) { return true; } @Override public boolean supports (Class<?> aClass) { return true; } }

    MyFilterSecurityInterceptor

    package com.experiment.blog.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import javax.servlet.*; import java.io.IOException; @Component public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired private FilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) { super.setAccessDecisionManager(myAccessDecisionManager); } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { //执行下一个拦截器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } }

    登陆成功返回token

    LoginSuccessHandler

    package com.experiment.blog.security; import com.alibaba.fastjson.JSON; import com.experiment.blog.common.CommonResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; 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; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override public void onAuthenticationSuccess (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { final UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getName()); final String token = jwtTokenUtil.generateToken(userDetails); returnToken(httpServletResponse,token); } public void returnToken(HttpServletResponse response,String token) throws IOException { response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); Map<String,String> map=new HashMap<>(); map.put("token",token); CommonResult<Map<String, String>> mapCommonResult = new CommonResult<>(200,"登入成功",map); writer.write(JSON.toJSONString(mapCommonResult)); writer.flush(); } }

    登陆失败

    LoginFailureHandler

    package com.experiment.blog.security; import com.alibaba.fastjson.JSON; import com.experiment.blog.common.CommonResult; import com.experiment.blog.exception.MyDefineException; 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; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { returnFailure(httpServletResponse); } public void returnFailure(HttpServletResponse response) throws IOException{ response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); CommonResult commonResult = new CommonResult<>(401,"用户名或密码错误"); writer.write(JSON.toJSONString(commonResult)); writer.flush(); } }

    SecurityConfig

    package com.experiment.blog.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 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.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.util.DigestUtils; @Configuration @EnableWebSecurity //开启Security @EnableGlobalMethodSecurity(prePostEnabled = true)//判断用户对某个控制层的方法是否具有访问权限 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired JwtUserDetailsService jwtUserDetailsService; @Autowired JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter; @Autowired LoginSuccessHandler loginSuccessHandler; @Autowired LoginFailureHandler loginFailureHandler; //先来这里认证一下 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService( jwtUserDetailsService ).passwordEncoder( new PasswordEncoder() { //对密码进行加密 @Override public String encode(CharSequence charSequence) { System.out.println("/*********charSequence.toString()"+charSequence.toString()); return new BCryptPasswordEncoder().encode(charSequence); } //对密码进行判断匹配 @Override public boolean matches(CharSequence charSequence, String s) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder.matches(charSequence,s); } } ); } //拦截在这配 @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/login**","/blog/user-blog/insert").permitAll() .anyRequest().authenticated(); http.addFilterAt(usernamePasswordFilter(),UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthorizationTokenFilter, UsernamePasswordAuthenticationFilter.class); } //加密 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override public void configure(WebSecurity web){ web.ignoring().antMatchers("/js/**","/css/**","/fail_url","/static/**","/img/**","/img/carousel/**","/swagger-ui.html","/webjars/**","/v2/**","/swagger-resources/**"); } @Bean public UsernamePasswordFilter usernamePasswordFilter() throws Exception { UsernamePasswordFilter filter=new UsernamePasswordFilter(); //重用WebSecurityConfigurerAdapter配置的AuthenticationManager filter.setAuthenticationManager(super.authenticationManager()); filter.setFilterProcessesUrl("/login"); filter.setAuthenticationSuccessHandler(loginSuccessHandler); filter.setAuthenticationFailureHandler(loginFailureHandler); return filter; }

    参考

    https://www.cnblogs.com/pjjlt/p/10960690.htmlhttps://www.cnblogs.com/foshuo-cv/archive/2020/05/07/12842075.html
    Processed: 0.012, SQL: 9