springboot在前后端分离项目中,集成springsecurity,集成jwt生成token,实现单点登录

该文章介绍了如何将SpringSecurity集成到SpringBoot项目中,以实现基于token的前后端分离登录验证、单点登录功能。通过重写UserDetailsService、登录成功和失败处理器、认证过滤器以及权限不足和未登录的拦截器,实现了详细的认证和授权流程。同时,使用了JWT进行令牌生成和验证,并结合注解进行权限控制。
摘要由CSDN通过智能技术生成

springSecurity集成springboot

将springSecurity集成springboot,前后端分离的方式开发,使用token验证,实现单点登录,在权限控制方面,使用注解的方式进行控制

引入依赖:

 		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

代码实现:

  • UserDetailsService接口重写登录逻辑
  • 登录成功拦截器的重写
  • 登录失败拦截器的重写
  • 认证逻辑过滤器的重写
  • 认证权限不足的拦截
  • 未登录,进行访问权限的拦截
  • 登出成功的拦截
java代码配置类:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Resource
    private AuthenticationFailureHandler authenticationFailureHandler;
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private AccessDeniedHandler myAccessDeniedHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new MD5Util());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginProcessingUrl("/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .usernameParameter("account")
        .and().csrf().disable();

        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(new MyLogoutSuccessHandler());

        http.addFilter(new MyBasicAuthenticationFilter(authenticationManager()))
                .httpBasic();

        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(myAccessDeniedHandler);

        // 管理员才可以查看swagger文档
//        http.authorizeRequests()
//                .antMatchers("/swagger-ui.html").hasAnyRole("ROLE_admin");
//        http.authorizeRequests()
//                .antMatchers("/user/register").permitAll()
//                .anyRequest().authenticated(); // 所有请求都拦截,使用注解的方式权限控制时,不使用这个,



    }
}

UserDetailsService接口重写登录逻辑:
// MyUserDetailsService implements UserDetailsService
@Service
public class MyUserDetailsServiceImpl implements MyUserDetailsService {

    @Resource
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        if (!StringUtils.hasText(account)){
            throw new RuntimeException("用户名不能为空!!!");
        }
        User user = (User) userService.getUserByName(account).getData();
        if (user == null){
            throw new RuntimeException("此账号还未注册");
        }
        List<GrantedAuthority> grantedAuthorityList =
                AuthorityUtils.commaSeparatedStringToAuthorityList(user.getAdmin() == 0 ? "ROLE_admin" : "ROLE_normal");
        return new org.springframework.security.core.userdetails.User(user.getAccount(),user.getPassword(),grantedAuthorityList);
    }
}

登录成功拦截器的重写:
@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Resource
    private UserService userService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        
        String account = String.valueOf(request.getParameter("account"));
        User user = (User) userService.findUserByAccount(account).getData();
		// 得到权限
        String permissions = user.getAdmin() == 0 ? "ROLE_normal" : "ROLE_admin" ;
        String token = JwtUtil.createToken(user.getId(),user.getAccount(),permissions);
		// token 发给前端
        response.setHeader("Authorization",token);
        response.setHeader("Access-Control-Expose-Headers", "Authorization");
        log.info("用户: [{}] 登录成功!!! , 权限为: [{}]",account , permissions);
        // SecurityContextHolder 赋值,用于授权
        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(user.getAccount(),token,
                        AuthorityUtils.commaSeparatedStringToAuthorityList(permissions))
        );
        ResponseUtil.out(response,Result.success(token));
    }
}

登录失败拦截器的重写:
@Component
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        log.warn("用户: [{}] 尝试登录,登录失败!!!",httpServletRequest.getParameter("account"),e);
        // 此为手动抛出异常
        if (e.getCause()!=null){
            ResponseUtil.out(httpServletResponse,Result.fail(555,e.getCause().getMessage()));
        }
        ResponseUtil.out(httpServletResponse, Result.fail(HttpCode.PASSWORD_ERROR));
    }
}
认证逻辑过滤器的重写:
/**
 * 重写授权的逻辑
 */
@Slf4j
public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {


    public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String token = request.getHeader("Authorization");
        // 没有token,视为未登录
        if (!StringUtils.hasText(token)) {
            chain.doFilter(request,response);
            return;
        }
        Claims claims = JwtUtil.decryptToken(token);
        if (claims == null){
            ResponseUtil.out(response, Result.fail(HttpCode.TOKEN_ERROR));
            return;
        }

        Long id = Long.valueOf(claims.getId());
        String account = claims.getSubject();
        String permissions = (String) claims.get("permissions");
        log.info("正在授权中-----id为: [{}] , 账号为: [{}] , 具有的权限: {}" ,id,account,permissions);
		// 认证成功,给SecurityContextHolder填充数据,用于验证授权
        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(account,token,
                        AuthorityUtils.commaSeparatedStringToAuthorityList(permissions))
        );
        chain.doFilter(request,response);
    }
}

认证权限不足的拦截:
/**
 * 登录的用户,权限不足的拦截
 */
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {

        ResponseUtil.out(httpServletResponse, Result.fail(HttpCode.NO_PERMISSIONS));
    }
}

未登录,进行访问权限的拦截:
/**
 * 未登录的用户,进行拦截
 */
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(httpServletResponse, Result.fail(HttpCode.NO_LOGIN));
    }
}

登出成功的拦截:
@Slf4j
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        if (authentication != null){
            new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
        }
        ResponseUtil.out(httpServletResponse, Result.success());
    }
}
工具类
// SecurityContextHolder 类中存的是认证用户的信息
public class SecurityContextUtil {

    /**
     * 通过SpringSecurity的上下文获取用户的token,得到id
     * @return
     */
    public static Long getCurrentUserId(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) return null;
        String token = (String) authentication.getCredentials();
        if (!StringUtils.hasText(token)){
            return null;
        }
        Claims claims = JwtUtil.decryptToken(token);
        if (claims == null){
            return null;
        }
        return Long.valueOf(claims.getId());
    }
}

/**
 * 自定义加密方式,盐值改变会导致一些用户无法登录
 */
public class MD5Util implements PasswordEncoder {
    // 盐值
    private static String salt = PropertiesConfig.getSalt();

    /**
     * 加密
     * 加密两次,第二次加密放上盐值
     * @param charSequence 字符串
     * @return
     */
    @Override
    public String encode(CharSequence charSequence) {
        String data = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
        return DigestUtils.md5DigestAsHex((data+salt).getBytes());
    }

    public static String encode(String charSequence){
        String data = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
        return DigestUtils.md5DigestAsHex((data+salt).getBytes());
    }
    public static String encode(String charSequence,String salt){
        String data = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
        return DigestUtils.md5DigestAsHex((data+salt).getBytes());
    }

    /**
     * 匹配是否相等
     * @param charSequence 未加密的字符串
     * @param s 已加密的字符串
     * @return
     */
    @Override
    public boolean matches(CharSequence charSequence, String s) {
        if (!StringUtils.hasText(charSequence)) return false;
        String data = encode(charSequence);
        return data.equals(s);
    }
    public static boolean matches(String charSequence,String salt, String encrypt) {
        if (!StringUtils.hasText(charSequence)) return false;
        String data = DigestUtils.md5DigestAsHex(charSequence.getBytes());
        data = DigestUtils.md5DigestAsHex((data+salt).getBytes());
        return data.equals(encrypt);
    }

}
// token工具
@Slf4j
public class JwtUtil {

    private static String key = PropertiesConfig.getKey();
    private static Integer time = PropertiesConfig.getTime();

    public static String createToken(Map<String, String> map) {

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, time);

        JwtBuilder jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, key);
        map.forEach((key, value) -> {
            jwtBuilder.claim(key, value);
        });
        // 设置 签名时间, 过期时间
        String token = jwtBuilder.setIssuedAt(new Date())
                .setExpiration(calendar.getTime()).compact();

        return token;
    }

    public static String createToken(Long id, String account, String permissions) {

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, time);
        log.info("有效时间: {}",time);
        JwtBuilder jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, key);
        jwtBuilder.setId(String.valueOf(id));
        jwtBuilder.setSubject(account);
        jwtBuilder.claim("permissions",permissions);
        // 设置 签名时间, 过期时间
        String token = jwtBuilder.setIssuedAt(new Date())
                .setExpiration(calendar.getTime()).compact();

        return token;
    }

    public static Claims decryptToken(String token) {
        try {
            Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
            return claims;
        } catch (Exception e) {
            log.info("解析token失败");
            return null; // 解密失败则返回null
        }

    }

}


使用:
@RestController
@RequestMapping("/category")
public class CategoryController {
    @Resource
    private CategoryService categoryService;

    @GetMapping("/all")
    @PreAuthorize("permitAll()")
    public Result all(){
        return categoryService.all();
    }

    @PostMapping("/add")
    @PreAuthorize("hasAnyRole('admin')")
    public Result add(@RequestBody @Validated CategoryAddParam categoryAddParam){
        return categoryService.add(categoryAddParam);
    }

    @PostMapping("/update")
    @PreAuthorize("hasAnyRole('admin')")
    public Result update(@RequestBody @Validated CategoryUpdateParam categoryUpdateParam){
        return categoryService.update(categoryUpdateParam);
    }

    @PostMapping("/delete")
    @PreAuthorize("hasAnyRole('admin')")
    public Result delete(@RequestBody Map<String,Long> map){
        Long id = map.get("id");
        if (id == null){
            return Result.fail(HttpCode.PARAM_ERROR);
        }
        return categoryService.delete(id);
    }


    /**
     * 根据名称查找类别
     */
    @PostMapping("/findCategoryByName")
    @PreAuthorize("permitAll()")
    public Result findCategoryByName(@RequestBody Map<String,String> map){
        String categoryName = map.get("categoryName");
        return categoryService.findCategoryByName(categoryName);
    }

    @PostMapping("fuzzy")
    @PreAuthorize("permitAll()")
    public Result fuzzy(@RequestBody CategoryFuzzyParam categoryFuzzyParam){
        return categoryService.fuzzy(categoryFuzzyParam);
    }

    @PostMapping("hot")
    @PreAuthorize("permitAll()")
    public Result hot(@RequestBody(required = false) Map<String,Integer> map){
        if (map == null) map = new HashMap<>();
        Integer limit = map.get("limit");
        return categoryService.hot(limit);
    }

    /**
     * 上传图片
     */
    @PostMapping("/uploadAvatar")
    @PreAuthorize("hasAnyRole('admin')")
    public Result uploadAvatar(@RequestParam("image")MultipartFile file) throws IOException {
        String url = FileUploadUtil.upload(PropertiesConfig.getUploadCategoryDir(), file, FileUploadUtil.IMAGE_ALLOWED_EXTENSION);
        return Result.success(FileUploadUtil.transformUrl(url));
    }


}

致此集成springSecurity完毕,若我的代码有不足之处,还请大家斧正。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot和Vue.js是两个非常流行的技术栈,可以非常好地实现前后端分离的开发模式。SecurityJWT是两个很好的工具,可以帮助我们实现安全的登录和授权机制。 以下是实现Spring Boot和Vue.js前后端分离的步骤: 1.创建Spring Boot工程 首先,我们需要创建一个Spring Boot工程,可以使用Spring Initializr来生成一个基本的Maven项目,添加所需的依赖项,包括Spring SecurityJWT。 2.配置Spring SecuritySpring Security,我们需要定义一个安全配置类,该类将定义我们的安全策略和JWT的配置。在这里,我们可以使用注解来定义我们的安全策略,如@PreAuthorize和@Secured。 3.实现JWT JWT是一种基于令牌的身份验证机制,它使用JSON Web Token来传递安全信息。在我们的应用程序,我们需要实现JWT生成和验证机制,以便我们可以安全地登录和授权。 4.配置Vue.js 在Vue.js,我们需要创建一个Vue.js项目,并使用Vue CLI来安装和配置我们的项目。我们需要使用Vue Router来定义我们的路由,并使用Axios来发送HTTP请求。 5.实现登录和授权 最后,我们需要实现登录和授权机制,以便用户可以安全地登录和访问我们的应用程序。在Vue.js,我们可以使用Vue Router和Axios来发送HTTP请求,并在Spring Boot使用JWT来验证用户身份。 总结 以上是实现Spring Boot和Vue.js前后端分离的步骤,我们可以使用SecurityJWT实现安全的登录和授权机制。这种开发模式可以让我们更好地实现前后端分离,提高我们的开发效率和应用程序的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值