SpringSecurity+Jwt做前后端分离权限认证

说在前面的话

这里是接上一篇,万字长文 基于SpringBoot整合SpringSecurity的认证授权(角色+权限) 真案列、有数据库、有源码,上一篇写的很细但是不是前后端分离,这一篇把前后端分离补上,数据库和Mapper模块都和前面的一致就不累述了,以下都是我自己的理解,如果什么地方有问题,请各位前辈指出,感激不尽。

项目目录在这里插入图片描述

其实变化也不大,就新增了拦截器和Service,以及一个工具类,当然配置文件中变换还是蛮大的,

在这里插入图片描述

Jwt工具类(新增)

嗯,在本文中会用到Jwt,关于Jwt嗯,因为我学习的时候没有写文章,后面可能会出一篇,好吧又给自己挖一个坑,这里我就默认大家会JWT了,当然这个项目的源码我也会在后面分享出来的。
首先是编写Jwt工具类,如下

package work.zx.unitl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import work.zx.entity.UserVo;

import java.util.Date;
import java.util.List;

public class JwtUtil {

    // 主题
    private static final String SUBJECT = "zx";

    // jwt的token有效期,
    //private static final long EXPIRITION = 1000L * 60 * 60 * 24 * 7;//7天
    private static final long EXPIRITION = 1000L * 60 * 30;   // 半小时

    // 加密key(黑客没有该值无法篡改token内容)
    private static final String APPSECRET_KEY = "zxdc";

    // 用户url权限列表key
    private static final String AUTH_CLAIMS = "auth";


    /**
     * TODO  生成token
     *
     * @param user
     * @return java.lang.String
     * @date 2020/7/6 0006 9:26
     */
    public static String generateToken(UserVo user) {
        String token = Jwts
                .builder()
                // 主题
                .setSubject(SUBJECT)
                // 添加jwt自定义值
                .claim(AUTH_CLAIMS, user.getAutk())
                .claim("username", user.getUsername())
                .claim("userId", user.getUid())
                .setIssuedAt(new Date())
                // 过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                // 加密方式,加密key
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
        return token;
    }

    /**
     * 获取用户Id
     *
     * @param token
     * @return
     */
    public static String getUserId(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("userId").toString();
    }

    /**
     * 获取用户名
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    /**
     * 获取用户角色的权限列表, 没有返回空
     *
     * @param token
     * @return
     */
    public static List<SimpleGrantedAuthority> getUserAuth(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        List auths = (List) claims.get(AUTH_CLAIMS);
        String json = JSONArray.toJSONString(auths);

        return JSON.parseArray(json, SimpleGrantedAuthority.class);
    }

    /**
     * 验证是否过期
     *
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        System.out.println("过期时间: " + claims.getExpiration());
        return claims.getExpiration().before(new Date());
    }

}

登录拦截器

这个不是必须的,也可以自己写一个登录接口,然后认证成功后将权限信息生成Token传给前端,我项目中就是使用的这种方式,这个dome我还是把登录拦截器弄出来了。
源码如下,

package work.zx.Config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.HandlerExceptionResolver;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;
import work.zx.filter.JWTLoginFilter;
import work.zx.filter.JWTValidFilter;
import work.zx.service.AuthenticationProviderImpl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;
    //用户表
    @Autowired
    private UserMapper userMapper;
    
    //角色表
    @Autowired
    private RoleMapper roleMapper;

    //资源表
    @Autowired
    private PermissionMapper permissionMapper;
    
    
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }


    @Autowired
    private AuthenticationProviderImpl authenticationProvider;

    /**
     * TODO 授权
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {


        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry eiur = http.authorizeRequests();
        eiur
                // 登录接口不需要权限控制,可删除,目前该接口不在权限列表中
                .antMatchers("/auth/login", "POST").permitAll()

                // 设置JWT过滤器
                .and()
                .addFilter(new JWTValidFilter(authenticationManager(), resolver))
                .addFilter(new JWTLoginFilter(authenticationManager(), resolver)).csrf().disable()

                // 剔除session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 开启跨域访问
        http.cors().disable();
        // 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
        http.csrf().disable();
        // iframe 跳转错误处理 Refused to display 'url' in a frame because it set 'X-Frame-Options' to 'deny'
        http.headers().frameOptions().disable();

    }


}

授权拦截器(新增)

当然这里是我这样叫,这里是拿到请求中携带的token,将token中权限部分取出来,然后保存到Security的Authentication授权管理器中,给我们后面注入那个AuthenticationProviderImpl,

package work.zx.filter;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import work.zx.unitl.JwtUtil;

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.List;

public class JWTValidFilter extends BasicAuthenticationFilter {

    // 异常处理类
    private HandlerExceptionResolver resolver;

    /**
     * SecurityConfig 配置中创建该类实例
     */
    public JWTValidFilter(AuthenticationManager authenticationManager, HandlerExceptionResolver resolver) {
        // 获取授权管理
        super(authenticationManager);
        // 获取异常处理类
        this.resolver = resolver;
    }


    /**
     * 拦截请求
     *
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取token, 没有token直接放行
        System.out.println("开始授权验证");
        String token = request.getHeader("token");

        if (StringUtils.isEmpty(token) || "null".equals(token)) {
            super.doFilterInternal(request, response, chain);
            // 获取token, 没有token直接放行
            return;
        }

        // 有token进行权限验证
        List<SimpleGrantedAuthority> userAuthList = null;
        String username = null;
        try {
            //  权限列表
            userAuthList = JwtUtil.getUserAuth(token);
            //  获取账号
            username = JwtUtil.getUsername(token);

        } catch (SignatureException ex) {
            resolver.resolveException(request, response, null, new Exception( "JWT签名与本地计算签名不匹配"));
            return;
        } catch (ExpiredJwtException ex) {
            resolver.resolveException(request, response, null, new Exception( "登录过期"));
            return;
        } catch (Exception e) {
            resolver.resolveException(request, response, null, new Exception( "JWT解析错误"));
            return;
        }

        //  添加账户的权限信息,和账号是否为空,然后保存到Security的Authentication授权管理器中
        if (!StringUtils.isEmpty(username) && userAuthList != null) {
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, null, userAuthList));
        }

        super.doFilterInternal(request, response, chain);
    }

}

AuthenticationProviderImpl

这个类实现了AuthenticationProvider接口,这个接口就两个方法
Authentication authenticate(Authentication authentication) throws AuthenticationException;
源码注释中说 这个是做身份认证的,传入一个身份验证请求对象。当然这些方法的调用就是SpringSecurity自己的事儿了,我们只需要重写即可使用,其实我自己也还没弄清楚。

package work.zx.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Component
public class AuthenticationProviderImpl implements AuthenticationProvider {

    //用户表
    @Autowired
    private UserMapper userMapper;

    //角色表
    @Autowired
    private RoleMapper roleMapper;

    //资源表
    @Autowired
    private PermissionMapper permissionMapper;


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //拿到登录传过来的信息
        String username = (String) authentication.getPrincipal();

        String password = (String) authentication.getCredentials();

        //存放权限的
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();

        UserVo userVo = userMapper.selectBYUserName(username);

        if (userVo == null || !password.equals(userVo.getPassword())) {
            System.out.println("用户名或者密码输入错误");
            throw new UsernameNotFoundException("用户名或者密码输入错误");
        }


        //拿到用户角色
        List<Role> roles = roleMapper.selectByUserId(userVo.getUid());
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            //拿到用户具有的权限
            List<Permission> permissionList = permissionMapper.selectByRoleId(role.getRid());
            for (Permission permission : permissionList) {
                //添加到
                authorities.add(new SimpleGrantedAuthority(permission.getStr()));
            }
        }
        userVo.setAutk(authorities);

        authentication = new AbstractAuthenticationToken(new ArrayList<>()) {

            @Override
            public Object getCredentials() {
                return userVo.getPassword();
            }

            @Override
            public Object getPrincipal() {
                return userVo;
            }

        };

        authentication.setAuthenticated(true);

        return authentication;
    }



    @Override
    public boolean supports(Class<?> authentication) {

        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

}

SpringSecurity配置文件

package work.zx.Config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.HandlerExceptionResolver;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;
import work.zx.filter.JWTLoginFilter;
import work.zx.filter.JWTValidFilter;
import work.zx.service.AuthenticationProviderImpl;



@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }


    @Autowired
    private AuthenticationProviderImpl authenticationProvider;

    /**
     * TODO 授权
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {


        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry eiur = http.authorizeRequests();
        eiur
                // 登录接口不需要权限控制,可删除,目前该接口不在权限列表中
                .antMatchers("/auth/login", "POST").permitAll()

                // 设置JWT过滤器
                .and()
                .addFilter(new JWTValidFilter(authenticationManager(), resolver))
                .addFilter(new JWTLoginFilter(authenticationManager(), resolver)).csrf().disable()

                // 剔除session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 开启跨域访问
        http.cors().disable();
        // 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
        http.csrf().disable();
        // iframe 跳转错误处理 Refused to display 'url' in a frame because it set 'X-Frame-Options' to 'deny'
        http.headers().frameOptions().disable();

    }


}

暂时就先到这里就结束了,后面等我更近一步会更新的。源码在下面。

链接:https://pan.baidu.com/s/1vuYW_dquZoVmPwWL2u7j1g
提取码:m18u

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot是一个基于Spring框架的快速开发Web应用程序的框架,Spring SecuritySpring框架的安全模块,JWT是一种用于身份验证的开放标准。Vue是一种流行的JavaScript框架,用于构建用户界面。 结合这些技术,可以实现前后端分离的登录、权限管理和Token管理。具体步骤如下: 1. 在Spring Boot项目中导入Spring SecurityJWT的Maven依赖。 2. 配置Spring Security,包括创建用户、角色和权限等。 3. 创建一个JWT工具类,用于生成和解析Token。 4. 创建一个登录接口,接收用户名和密码,验证用户信息,生成Token并返回给前端。 5. 创建一个Token验证过滤器,用于验证请求中的Token是否有效。 6. 在Vue项目中使用Axios发送登录请求,获取Token并保存到本地存储中。 7. 在Vue项目中使用Vue Router和VueX进行路由和状态管理。 8. 创建一个路由守卫,用于验证用户是否登录和是否有权限访问某些页面。 9. 在需要进行身份验证的请求中添加Token。 下面是一个简单的示例代码,仅供参考: 后端代码: ```java // 配置Spring Security @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } } // 创建一个JWT工具类 public class JwtUtils { private static final String SECRET_KEY = "mySecretKey"; private static final long EXPIRATION_TIME = 86400000; // 24 hours public static String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject(); } public static boolean validateToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private static boolean isTokenExpired(String token) { Date expiration = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration(); return expiration.before(new Date()); } } // 创建一个登录接口 @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername()); String token = JwtUtils.generateToken(userDetails); return ResponseEntity.ok(new JwtAuthenticationResponse(token)); } } // 创建一个Token验证过滤器 public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); String username = jwtUtils.getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtUtils.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } } // 创建一个自定义的AuthenticationEntryPoint @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } } // 创建一个自定义的UserDetailsService @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>()); } } // 创建一个实体类User和一个接口UserRepository @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "username") private String username; @Column(name = "password") private String password; // getters and setters } @Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` 前端代码: ```javascript // 在Vue项目中使用Axios发送登录请求 axios.post('/api/auth/login', { username: 'admin', password: 'password' }).then(response => { localStorage.setItem('token', response.data.token); }); // 在需要进行身份验证的请求中添加Token axios.get('/api/users', { headers: { Authorization: 'Bearer ' + localStorage.getItem('token') } }); // 创建一个路由守卫 router.beforeEach((to, from, next) => { const publicPages = ['/login', '/register']; const authRequired = !publicPages.includes(to.path); const loggedIn = localStorage.getItem('token'); if (authRequired && !loggedIn) { return next('/login'); } next(); }); // 使用VueX进行状态管理 const store = new Vuex.Store({ state: { isLoggedIn: !!localStorage.getItem('token') }, mutations: { login(state) { state.isLoggedIn = true; }, logout(state) { state.isLoggedIn = false; } }, actions: { login({ commit }) { return new Promise(resolve => { axios.post('/api/auth/login', { username: 'admin', password: 'password' }).then(response => { localStorage.setItem('token', response.data.token); commit('login'); resolve(); }); }); }, logout({ commit }) { return new Promise(resolve => { localStorage.removeItem('token'); commit('logout'); resolve(); }); } } }); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值