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。

java 复制代码
@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();
    }
}

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

JWT认证和授权过滤器

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

java 复制代码
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);
    }
}
java 复制代码
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;
    }
}

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

用户服务和认证

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

java 复制代码
@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;
    }
}

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

Redis存储Token

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

java 复制代码
@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);
    }
}

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

前台实现

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

登录组件

vue 复制代码
<template>
  <div>
    <h1>用户登录</h1>
    <form @submit.prevent="login">
      <label for="username">用户名:</label>
      <input type="text" id="username" v-model="username" required>
      <br>
      <label for="password">密码:</label>
      <input type="password" id="password" v-model="password" required>
      <br>
      <button type="submit">登录</button>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    login() {
      // 调用后端API接口进行用户登录
      // 使用axios或其他HTTP库发送POST请求
    }
  }
};
</script>

路由配置

javascript 复制代码
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;

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

入口文件

javascript 复制代码
import Vue from 'vue';
import App from './App.vue';
import router from './router';

new Vue({el: '#app',
  router,
  render: h => h(App)
}).$mount('#app');

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

用户登录

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

javascript 复制代码
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);
    });
  }
}

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

权限校验

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

javascript 复制代码
export default {
  created() {
    if (!localStorage.getItem('token')) {
      this.$router.push('/login');
    }
  }
};

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

总结

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

相关推荐
passer9816 小时前
基于Vue的场景解决
前端·vue.js
游荡de蝌蚪6 小时前
快速打造Vue后台管理系统
前端·javascript·vue.js
haciii6 小时前
Spring Boot启动源码深度分析 —— 新手也能看懂的原理剖析
spring boot
阿杆8 小时前
为什么我建议你把自建 Redis 迁移到云上进行托管
redis·后端
间彧9 小时前
什么是Redis分布式锁,有何使用场景
redis
我是日安12 小时前
从零到一打造 Vue3 响应式系统 Day 10 - 为何 Effect 会被指数级触发?
前端·vue.js
艾小码12 小时前
还在硬邦邦跳转页面?Vue这3招让应用丝滑如德芙!
前端·javascript·vue.js
间彧12 小时前
Spring Boot项目中,Redis 如何同时执行多条命令
java·redis
泉城老铁12 小时前
Spring Boot对接抖音获取H5直播链接详细指南
spring boot·后端·架构
Olrookie13 小时前
ruoyi-vue(十五)——布局设置,导航栏,侧边栏,顶部栏
前端·vue.js·笔记