• Spring Boot + Vue的网上商城之springsecurity+jwt+redis实现用户权限认证实现


    Spring Boot + Vue的网上商城之springsecurity+jwt+redis实现用户权限认证实现

    在网上商城项目中,用户的安全性是非常重要的。为了实现用户权限认证和安全校验,我们可以使用Spring Security、JWT和Redis来实现。本篇博客将详细介绍后端和前台的实现过程,并提供相应的代码案例。

    思路

    当用户点击登录按钮时,前端发送一个POST请求到后端的登录接口,传递用户名和密码。后端接收到请求后,验证用户名和密码的正确性。如果验证通过,后端生成一个JWT(JSON Web Token),并将其返回给前端。

    前端接收到JWT后,将其存储在本地,例如使用localStorage。然后通过Vue Router进行页面跳转,跳转到需要进行权限校验的页面。

    在需要进行权限校验的页面组件中,可以在created钩子函数中检查本地存储中是否存在JWT。如果不存在,则表示用户未登录或登录已过期,可以通过Vue Router跳转到登录页面。

    需要注意的是,在每次向后端发送请求时,需要将JWT作为请求头的Authorization字段进行传递,以便后端进行权限校验。

    后端在接收到请求时,可以通过解析JWT来验证用户的身份和权限。JWT中通常包含用户ID、用户名、权限信息等。后端可以使用Spring Security的相关功能进行JWT的解析和校验。

    总结来说,用户登录和权限认证的流程可以简化为以下几个步骤:

    1. 用户在前端页面输入用户名和密码,并点击登录按钮。
    2. 前端发送POST请求到后端的登录接口,传递用户名和密码。
    3. 后端验证用户名和密码的正确性,如果验证通过,生成JWT并返回给前端。
    4. 前端接收到JWT后,将其存储在本地。
    5. 前端通过Vue Router进行页面跳转,跳转到需要进行权限校验的页面。
    6. 在需要进行权限校验的页面组件中,检查本地存储中是否存在JWT,如果不存在,则跳转到登录页面。
    7. 在每次向后端发送请求时,将JWT作为请求头的Authorization字段进行传递。
    8. 后端在接收到请求时,解析JWT并进行权限校验,校验通过则返回相应的数据或页面。

    后端实现

    Spring Security配置

    首先,在后端的Spring Boot项目中,我们需要配置Spring Security来实现用户权限认证。我们可以创建一个SecurityConfig类来配置Spring Security。

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserService userService;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                .authorizeRequests()
                    .antMatchers("/api/public/**").permitAll()
                    .anyRequest().authenticated()
                .and()
                    .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                    .addFilter(new JwtAuthorizationFilter(authenticationManager(), userService))
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    上述代码中,我们配置了允许访问/api/public/**路径的请求,其他请求需要进行认证。我们还添加了两个过滤器JwtAuthenticationFilterJwtAuthorizationFilter来处理JWT的认证和授权。

    JWT认证和授权过滤器

    接下来,我们来实现JWT的认证和授权过滤器。

    public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
        private AuthenticationManager authenticationManager;
    
        public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
            this.authenticationManager = authenticationManager;
            setFilterProcessesUrl("/api/login");
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            try {
                UserCredentials credentials = new ObjectMapper().readValue(request.getInputStream(), UserCredentials.class);
                return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                        credentials.getUsername(), credentials.getPassword(), new ArrayList<>()));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
            String token = Jwts.builder()
                    .setSubject(((User) authResult.getPrincipal()).getUsername())
                    .setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
                    .signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET)
                    .compact();
            response.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
    
        private UserService userService;
    
        public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserService userService) {
            super(authenticationManager);
            this.userService = userService;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
            String header = request.getHeader(SecurityConstants.HEADER_STRING);
            if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
                chain.doFilter(request, response);
                return;
            }
            UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request, response);
        }
    
        private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
            String token = request.getHeader(SecurityConstants.HEADER_STRING);
            if (token != null) {
                String username = Jwts.parser()
                        .setSigningKey(SecurityConstants.SECRET)
                        .parseClaimsJws(token.replace(SecurityConstants.TOKEN_PREFIX, ""))
                        .getBody()
                        .getSubject();
                if (username != null) {
                    User user = userService.loadUserByUsername(username);
                    return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                }
                return null;
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上述代码中,JwtAuthenticationFilter处理登录请求,将用户的认证信息封装为JWT,并添加到响应头中。JwtAuthorizationFilter处理其他请求,从请求头中获取JWT并进行认证和授权。

    用户服务和认证

    为了实现用户的认证和授权,我们需要创建一个用户服务实现类UserService

    @Service
    public class UserService 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(username);
            }
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上述代码中,我们通过用户名从数据库中获取用户信息。

    Redis存储Token

    为了增加用户登录的安全性,我们可以将JWT存储到Redis中,并设置过期时间。

    @Service
    public class TokenService {
    
        private RedisTemplate<String, Object> redisTemplate;
    
        public TokenService(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        public void saveToken(String username, String token) {
            redisTemplate.opsForValue().set(username, token, SecurityConstants.EXPIRATION_TIME, TimeUnit.MILLISECONDS);
        }
    
        public boolean validateToken(String username, String token) {
            String storedToken = (String) redisTemplate.opsForValue().get(username);
            return storedToken != null && storedToken.equals(token);
        }
    
        public void deleteToken(String username) {
            redisTemplate.delete(username);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上述代码中,我们使用redisTemplate将用户名和JWT存储到Redis中,并设置过期时间。在校验Token时,我们从Redis中获取存储的Token进行比较。

    前台实现

    在前台,我们使用Vue来实现用户登录和权限认证的功能。我们需要创建相应的组件和路由来实现用户登录和权限校验。

    登录组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    路由配置

    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import Login from './components/Login.vue';
    import ProductList from './components/ProductList.vue';
    
    Vue.use(VueRouter);
    
    const routes = [
      { path: '/login', component: Login },
      { path: '/products', component: ProductList, meta: { requiresAuth: true } }
    ];
    
    const router = new VueRouter({
      routes
    });
    
    router.beforeEach((to, from, next) => {
      const token = localStorage.getItem('token');
      if (to.matched.some(record => record.meta.requiresAuth)) {
        if (!token) {
          next('/login');
        } else {
          next();
        }
      } else {
        next();
      }
    });
    
    export default router;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在上述代码中,我们配置了两个路由,/login用于用户登录,/products用于展示商品列表。在/products路由中,我们设置了requiresAuthtrue,表示需要进行权限校验。在路由导航守卫中,我们检查是否存在Token,如果不存在则跳转到登录页面。

    入口文件

    import Vue from 'vue';
    import App from './App.vue';
    import router from './router';
    
    new Vue({el: '#app',
      router,
      render: h => h(App)
    }).$mount('#app');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在入口文件中,我们创建了一个Vue实例,并将路由配置和根组件传入。然后将Vue实例挂载到#app元素上。

    用户登录

    在登录组件中,我们需要调用后端API接口进行用户登录,并将返回的Token保存到本地存储中。

    methods: {
      login() {
        axios.post('/api/login', {
          username: this.username,
          password: this.password
        })
        .then(response => {
          const token = response.data.token;
          localStorage.setItem('token', token);
          this.$router.push('/products');
        })
        .catch(error => {
          console.error(error);
        });
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在上述代码中,我们使用axios库发送POST请求到后端的登录接口。如果登录成功,会返回一个包含Token的响应。我们将Token保存到本地存储中,并使用$router.push方法跳转到商品列表页面。

    权限校验

    在商品列表组件中,我们需要进行权限校验,只有在用户登录成功并且存在Token的情况下才能访问该页面。

    export default {
      created() {
        if (!localStorage.getItem('token')) {
          this.$router.push('/login');
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在上述代码中,我们在组件创建时检查本地存储中是否存在Token,如果不存在则跳转到登录页面。

    总结

    本文介绍了如何使用Spring Security和Vue实现用户登录和权限认证的功能。通过使用JWT和Redis存储Token,我们可以实现无状态的用户认证,提高系统的可扩展性和安全性。同时,通过使用Vue的路由导航守卫,我们可以实现前端的权限校验,确保只有登录用户才能访问受限资源。希望本文能对你理解和实现用户登录和权限认证功能有所帮助。

  • 相关阅读:
    linux 基本常用命令
    linux下查找文件的相关命令
    本地服务器设置静态ip方法与原理
    分析Python7个爬虫小案例(附源码)
    (a == 1 && a == 2 && a == 3)不可以为true?
    基于java班费收支管理系统计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
    PHP如何对二维数组(多维数组)进排序
    SSL、TLS拒绝服务攻击
    【Linux】第五站:Linux权限
    App 启动流程全解析
  • 原文地址:https://blog.csdn.net/qq_22593423/article/details/132844861