单点登录实现

单点登录实现方案:

①SpringBoot整合SpringSecurity+JWT实现单点登录

  • ②SpringBoot+OAuth2+SpringSecurity+JWT实现单点登录
  • ③Springboot整合shiro+jwt实现单点登录

①SpringBoot整合SpringSecurity+JWT实现单点登录方案:

1.单点登录的概念

Single Sign On(简称SSO)。业务初始,我们所有的功能都在一个系统,比如登录系统、订单系统、购物车系统都在一台机子,使用的都是同一台机器的登录系统。随着业务的发展,业务进行了拆分,分割出多个业务系统,分别部署在不同的机子上,那用户的登录信息只在其中一台机子上面,要想获取用户信息,就得每次都登录一遍登录系统,那就很繁琐,无论是性能还是用户体验都不是最佳方案。所以,后面演化出了SSO,简单来说,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。

2.多系统登录的问题解决

众所周知,HTTP是无状态的协议,这意味着服务器无法确认用户的信息。于是乎:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息,这个通行证我们理解为token,怎么做到无状态和跨域,我们后面使用的技术是JWT。

3.session实现的单点登录问题

4.实现方式及效果

用户不带token访问系统B,系统B响应状态码401(需要认证)
用户登录系统A,系统A校验用户名密码成功,生成并响应token及状态码200
用户没有登录系统B而是携带系统A响应的token去访问系统B
系统B解析token并进行权限校验,如果系统A的token解析出来发现权限不足访问资源则响应403,权限验证成功则响应正常的json数据
访问系统C、系统D或分布式集群亦是如此。

5.认证思路分析

用户认证:
由于分布式项目,多数是前后端分离的架构设计,我们要满足可以接受异步post的认证请求参数,需要修改UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法,让其能够接收请求体。
另外,默认successfulAuthentication方法在认证通过后,是把用户信息直接放入session就完事了,现在我们需要修改这个方法,在认证通过后生成token并返回给用户。

身份校验:
原来BasicAuthenticationFilter过滤器中doFilterInternal方法校验用户是否登录,就是看session中是否有用户信息,我们要修改为,验证用户携带的token是否合法,并解析出用户信息,交给SpringSecurity,以便于后续的授权功能可以正常使用。

6.过滤器和登录拦截全局配置

我们实现单点登录配置了两个过滤器类和一个登录拦截全局配置:

①UserAuthenticationAndGeneralToeknFilter:

这个过滤器的实现以及主要工作:

1.过滤器实现:

extends UsernamePasswordAuthenticationFilter类,重写attemptAuthentication(…)、unsuccessfulAuthentication(…)、successfulAuthentication(…)方法。

2.主要工作:

2.1设置了登录请求拦截,拦截登录接口请求

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        // 登录请求拦截
        // SecurityConstants.AUTH_LOGIN_URL="/user/login";
        setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);
    }

2.2拦截登录请求的用户名和密码,安全框架进行用户名和密码认证

    /**
     * 用户信息认证
     *
     * @param request  请求体
     * @param response 相应体
     * @return Authentication
     * @throws AuthenticationException 认证异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
        // 进行用户名和密码认证-登录拦截全局配置类重写的protected UserDetailsService userDetailsService()的loadUserByUsername方法,可以进行自定义逻辑的用户信息认证,具体的实现在loadUserByUsername方法,这里是讲的一个拦截处理的流程
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(request.getParameter("userId"), request.getParameter("pwd"));
            return authenticationManager.authenticate(authRequest);
        } catch (Exception e) {
        // 认证异常我直接处理了,因为我发现当用户不存在的时候我们抛出来的是UsernameNotFoundException,但是unsuccessfulAuthentication接收到的异常却是BadCredentialsException(这涉及到一个属性配置,我配置了半天没搞通,放弃了),总是提示 用户名密码不正确,解决的办法就是可以自定义异常或者直接在这里捕获处理都行
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                PrintWriter out = response.getWriter();
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
                resultMap.put("msg", e.getMessage());
                out.write(new ObjectMapper().writeValueAsString(resultMap));
                out.flush();
                out.close();
            } catch (Exception outEx) {
                throw new RuntimeException(outEx);
            }
        }
        return null;
    }

2.3安全框架用户名和密码认证异常和成功的处理
异常自定义回写到header:

    /**
     * 认证失败异常捕捉返回-异常是安全配置类loadUserByUsername方法自定义抛出的
     *
     * @param request  请求体
     * @param response 相应体
     * @param failed   异常
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {

//        //清理上下文
//        SecurityContextHolder.clearContext();
//        //判断异常类
//        if (failed instanceof InternalAuthenticationServiceException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "认证服务不正常!");
//        } else if (failed instanceof UsernameNotFoundException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户不存在!");
//        } else if (failed instanceof BadCredentialsException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户密码是错的!");
//        } else if (failed instanceof AccountExpiredException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已过期!");
//        } else if (failed instanceof LockedException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已被锁!");
//        } else if (failed instanceof CredentialsExpiredException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户密码已失效!");
//        } else if (failed instanceof DisabledException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已被锁!");
//        }
    }

认证成功:生成token,回写到header:

    /**
     * 认证成功生成token
     *
     * @param request    请求体
     * @param response   响应体
     * @param chain      过滤器链
     * @param authResult 认证信息
     * @throws IOException IO异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication authResult) throws IOException {

        // 用户id
        String userId = request.getParameter("userId");

        // 用户角色
        UserDetails user = (UserDetails) authResult.getPrincipal();
        List<?> roles = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());

        // 加盐字符串
        byte[] signingKey = SecurityConstants.JWT_SECRET.getBytes();

        // token过期时间 每次登录成功续期864000000
        long expireTime = System.currentTimeMillis() + 864000000;
        Date expireDate = new Date(expireTime);

        // token生成
        String token = Jwts.builder()
                .signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
                .setHeaderParam("typ", SecurityConstants.TOKEN_TYPE) // JWT类型:jwt
                .setIssuer(SecurityConstants.TOKEN_ISSUER) // 放行方:字符串secure-api
                .setAudience(SecurityConstants.TOKEN_AUDIENCE) // 字符串secure-app
                .setSubject(user.getUsername())
                .setExpiration(expireDate)
                .claim("rol", roles)
                .compact();

        // 设置浏览器的header,设置token
        response.addHeader(SecurityConstants.TOKEN_HEADER, SecurityConstants.TOKEN_PREFIX + token);

        // 相关信息返回
        Map<String, Object> content = new HashMap<String, Object>();
        content.put("token", token);
        content.put("userId", userId);
        FMResponse fm = new FMResponse(1, "用户认证通过", content);
        String FmJson = JSON.toJSONString(fm, SerializerFeature.WriteMapNullValue);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().append(FmJson);

    }

好了,到这里我们第一个过滤器的工作流程就记录完了,主要工作是用户认证和认证之后异常处理或者成功之后的token生成处理。至于这个过滤器的使用,则是在登录拦截全局配置中使用,这个看配置就知道了。

完整代码:

/**
 * 用户名密码认证过滤器
 */
public class UserAuthenticationAndGeneralToeknFilter extends UsernamePasswordAuthenticationFilter {
    Logger logger = LoggerFactory.getLogger(UserAuthenticationAndGeneralToeknFilter.class);

    private final AuthenticationManager authenticationManager;

    public UserAuthenticationAndGeneralToeknFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);
    }

    private UserLoginFailRepository userLoginFailureRepository() {
        return SpringContext.getBean(UserLoginFailRepository.class);
    }

    private UserVerifiedCodeRepository userVerifiedCodeRepository() {
        return SpringContext.getBean(UserVerifiedCodeRepository.class);
    }

    private UserRepository userRepository() {
        return SpringContext.getBean(UserRepository.class);
    }

    /**
     * 用户信息认证
     *
     * @param request  请求体
     * @param response 相应体
     * @return Authentication
     * @throws AuthenticationException 认证异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(request.getParameter("userId"), request.getParameter("pwd"));
            return authenticationManager.authenticate(authRequest);
        } catch (Exception e) {
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                PrintWriter out = response.getWriter();
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
                resultMap.put("msg", e.getMessage());
                out.write(new ObjectMapper().writeValueAsString(resultMap));
                out.flush();
                out.close();
            } catch (Exception outEx) {
                throw new RuntimeException(outEx);
            }
//            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * 认证失败异常捕捉返回-异常是安全配置类loadUserByUsername方法自定义抛出的
     *
     * @param request  请求体
     * @param response 相应体
     * @param failed   异常
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {

//        //清理上下文
//        SecurityContextHolder.clearContext();
//        //判断异常类
//        if (failed instanceof InternalAuthenticationServiceException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "认证服务不正常!");
//        } else if (failed instanceof UsernameNotFoundException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户不存在!");
//        } else if (failed instanceof BadCredentialsException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户密码是错的!");
//        } else if (failed instanceof AccountExpiredException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已过期!");
//        } else if (failed instanceof LockedException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已被锁!");
//        } else if (failed instanceof CredentialsExpiredException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户密码已失效!");
//        } else if (failed instanceof DisabledException) {
//            write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已被锁!");
//        }
    }

    /**
     * 向浏览器响应一个json字符串
     *
     * @param response 响应对象
     * @param code     状态码
     * @param msg      响应信息
     */
    public void write(HttpServletResponse response, int code, String msg) {
        try {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Cache-Control", "no-cache");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.setStatus(code);
            PrintWriter out = response.getWriter();
            Map<String, Object> map = new HashMap<>();
            map.put("code", code);
            map.put("msg", msg);
            out.write(JsonUtils.toJson(map));
            out.flush();
            out.close();
        } catch (Exception e) {
            logger.error("响应出错:" + msg, e);
        }
    }

    /**
     * 认证成功生成token
     *
     * @param request    请求体
     * @param response   响应体
     * @param chain      过滤器链
     * @param authResult 认证信息
     * @throws IOException IO异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication authResult) throws IOException {

        // 用户id
        String userId = request.getParameter("userId");

        // 用户角色
        UserDetails user = (UserDetails) authResult.getPrincipal();
        List<?> roles = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());

        // 加盐字符串
        byte[] signingKey = SecurityConstants.JWT_SECRET.getBytes();

        // token过期时间 每次登录成功续期864000000
        long expireTime = System.currentTimeMillis() + 864000000;
        Date expireDate = new Date(expireTime);

        // token生成
        String token = Jwts.builder()
                .signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
                .setHeaderParam("typ", SecurityConstants.TOKEN_TYPE) // JWT类型:jwt
                .setIssuer(SecurityConstants.TOKEN_ISSUER) // 放行方:字符串secure-api
                .setAudience(SecurityConstants.TOKEN_AUDIENCE) // 字符串secure-app
                .setSubject(user.getUsername())
                .setExpiration(expireDate)
                .claim("rol", roles)
                .compact();

        // 设置浏览器的header,设置token
        response.addHeader(SecurityConstants.TOKEN_HEADER, SecurityConstants.TOKEN_PREFIX + token);

        // 相关信息返回
        Map<String, Object> content = new HashMap<String, Object>();
        content.put("token", token);
        content.put("userId", userId);
        FMResponse fm = new FMResponse(1, "用户认证通过", content);
        String FmJson = JSON.toJSONString(fm, SerializerFeature.WriteMapNullValue);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().append(FmJson);

    }

}
②DoFilterInternalAndJwtToeknParseFilter

这个过滤器的实现以及主要工作:非登录请求拦截,验证请求携带的token的合法性。

1.过滤器实现:extends BasicAuthenticationFilter,重写doFilterInternal()和getAuthentication()方法。

    /**
     * 非登录请求拦截
     *
     * @param request  请求体
     * @param response 响应体
     * @param chain    过滤器链
     * @throws IOException      IO异常
     * @throws ServletException 异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
		// 验证token的合法性
        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
        if (authenticationToken == null) {
            chain.doFilter(request, response);
            return;
        }

        //  如果用户被删除,阻止其使用。
        String userId = authenticationToken.getName();
        Optional<RemovedUserEntity> userEntityOptional = removedUserRepository().findByUserId(userId);
        if (userEntityOptional.isPresent()) {
            return;
        }

		// 放置到SecurityContextHolder,这样便完成了springsecurity和jwt的整合
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
		// 校验成功,准备分发请求到Servlet
        chain.doFilter(request, response);
    }
 /**
     * token认证
     *
     * @param request 请求体
     * @return UsernamePasswordAuthenticationToken
     */
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(com.hermes.config.SecurityConstants.TOKEN_HEADER);
        if (!StringUtils.isEmpty(token) && token.startsWith(com.hermes.config.SecurityConstants.TOKEN_PREFIX)) {
            try {
                byte[] signingKey = com.hermes.config.SecurityConstants.JWT_SECRET.getBytes();
                Jws<Claims> parsedToken = Jwts
                        .parserBuilder().setSigningKey(signingKey).build()
                        .parseClaimsJws(token.replace("Bearer ", ""));
                String username = parsedToken.getBody().getSubject();

                Collection<? extends GrantedAuthority> authorities = ((List<?>) parsedToken.getBody().get("rol"))
                        .stream()
                        .map(authority -> new SimpleGrantedAuthority((String) authority)).collect(Collectors.toList());

                if (!StringUtils.isEmpty(username)) {
                    return new UsernamePasswordAuthenticationToken(username, null, authorities);
                }
            } catch (ExpiredJwtException exception) {
                log.error("Request to parse expired JWT : {} failed : {}", token, exception.getMessage());
            } catch (UnsupportedJwtException exception) {
                log.error("Request to parse unsupported JWT : {} failed : {}", token, exception.getMessage());
            } catch (MalformedJwtException exception) {
                log.error("Request to parse invalid JWT : {} failed : {}", token, exception.getMessage());
            } catch (SignatureException exception) {
                log.error("Request to parse JWT with invalid signature : {} failed : {}", token, exception.getMessage());
            } catch (IllegalArgumentException exception) {
                log.error("Request to parse empty or null JWT : {} failed : {}", token, exception.getMessage());
            }
        }

        return null;
    }

好了,第二个过滤器也搞定了,针对的非登录请求的token合法性验证。

完整代码:


public class DoFilterInternalAndJwtToeknParseFilter extends BasicAuthenticationFilter {

    Logger log = LoggerFactory.getLogger(DoFilterInternalAndJwtToeknParseFilter.class);

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

    private RemovedUserRepository removedUserRepository() {
        return SpringContext.getBean(RemovedUserRepository.class);
    }

    /**
     * 非登录请求拦截
     *
     * @param request  请求体
     * @param response 响应体
     * @param chain    过滤器链
     * @throws IOException      IO异常
     * @throws ServletException 异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
		// 验证token的合法性
        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
        if (authenticationToken == null) {
            chain.doFilter(request, response);
            return;
        }

        //  如果用户被删除,阻止其使用。
        String userId = authenticationToken.getName();
        Optional<RemovedUserEntity> userEntityOptional = removedUserRepository().findByUserId(userId);
        if (userEntityOptional.isPresent()) {
            return;
        }

		// 放置到SecurityContextHolder,这样便完成了springsecurity和jwt的整合
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
		// 校验成功,准备分发请求到Servlet
        chain.doFilter(request, response);
    }

    /**
     * token认证
     *
     * @param request 请求体
     * @return UsernamePasswordAuthenticationToken
     */
 private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) throws ExpiredJwtException {
        String token = request.getHeader(com.hermes.config.SecurityConstants.TOKEN_HEADER);
        if (!StringUtils.isEmpty(token) && token.startsWith(com.hermes.config.SecurityConstants.TOKEN_PREFIX)) {
            try {
                byte[] signingKey = com.hermes.config.SecurityConstants.JWT_SECRET.getBytes();
                Jws<Claims> parsedToken = Jwts
                        .parserBuilder().setSigningKey(signingKey).build()
                        .parseClaimsJws(token.replace("Bearer ", ""));
                String username = parsedToken.getBody().getSubject();

                Collection<? extends GrantedAuthority> authorities = ((List<?>) parsedToken.getBody().get("rol"))
                        .stream()
                        .map(authority -> new SimpleGrantedAuthority((String) authority)).collect(Collectors.toList());

                if (!StringUtils.isEmpty(username)) {
                    return new UsernamePasswordAuthenticationToken(username, null, authorities);
                }
            }
            catch (ExpiredJwtException exception) {
                throw new ExpiredJwtException(null,null,exception.getMessage());
            } catch (UnsupportedJwtException exception) {
                throw new UnsupportedJwtException(exception.getMessage());
            } catch (MalformedJwtException exception) {
                throw new MalformedJwtException(exception.getMessage());
            } catch (SignatureException exception) {
                throw new SignatureException(exception.getMessage());
            } catch (IllegalArgumentException exception) {
                throw new IllegalArgumentException(exception.getMessage());
            }
        }

        return null;
    }

③登录拦截全局配置

主要工作:
配置放行和拦截接口
添加两个过滤器addFilter()
loadUserByUsername():登录接口的用户信息认证:比如:用户账户不存在,用户密码错误等相关验证校验,错误信息通过响应的异常抛出。

完整代码:

/**
 * 登录拦截全局配置
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserVerifiedCodeRepository userVerifiedCodeRepository;


    @Autowired
    UserRepository userRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and()
                .csrf().disable() // 关闭csrf验证(防止跨站请求伪造攻击)
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/user/register").permitAll() // 注册请求放行
//                .antMatchers(HttpMethod.POST, "/user/logout").permitAll() 
//                .antMatchers(HttpMethod.GET,"/historySend/findHistorySendBusinessTypes").permitAll()

                .anyRequest().authenticated() // 其他请求统统拦截进行身份认证
                .and()
                .addFilter(new com.h.config.UserAuthenticationAndGeneralToeknFilter(authenticationManager())) // 自定义认证过滤器
                .addFilter(new com.h.config.DoFilterInternalAndJwtToeknParseFilter(authenticationManager())) // token认证器 
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // session 无状态

    }

       @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                // 业务校验逻辑,具体的业务场景具体实现
                Optional<UserInfo> userInfo = userRepository.findByUserId(username);
                if (!userInfo.isPresent()){
                    throw new BadCredentialsException(username + "用户不存在,登录失败");
                }
                
                Optional<UserVerifiedCode> codeOptional = userVerifiedCodeRepository.findById(username);
                if (!codeOptional.isPresent()){
                    throw new BadCredentialsException(username + "验证码为空,登录失败");
                }
                String role = "USER";
                if(userInfo.get().getRole() != null){
                    role = userInfo.get().getRole();
                }
                
                return User.withUsername(username)
                        .password(passwordEncoder().encode(codeOptional.get().getCode()))
                        .roles(role)                        .build();
            }
        };
    }

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

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));

        corsConfiguration.addExposedHeader("LoginFailed");
        corsConfiguration.addAllowedHeader("LoginFailed");
        corsConfiguration.addExposedHeader("authorization");
        corsConfiguration.addAllowedHeader("authorization");
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));

        corsConfiguration.addExposedHeader("Content-Disposition");
        corsConfiguration.addAllowedHeader("Content-Disposition");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

}

7.引入的依赖

implementation ‘org.springframework.boot:spring-boot-starter-security’
implementation ‘io.jsonwebtoken:jjwt-api:0.11.1’
implementation ‘io.jsonwebtoken:jjwt-impl:0.11.1’
implementation ‘io.jsonwebtoken:jjwt-jackson:0.11.1’

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值