Bootstrap

SpringCloud+Nacos+Security+Vue单点登录记录

前言

最近把spring-cloud的注册中心改用了Nacos,顺便整合了一下spring-security,前端采用vue实现登录,废话不多说,开始了。

一、后端

1、nacos集成

a. 下载nacos。我是用的2.0版本以上

b. 解压,启动sh startup.sh -m standalone

c. http://localhost:8848/nacos/,账号nacos/nacos

d.pom依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、创建gateway集成security

SecurityConfig配置
SecurityWebFilterChain chain = http.formLogin()
        .loginPage("/login")
        // 登录成功handler
        .authenticationSuccessHandler(jsonServerAuthenticationSuccessHandler)
        // 登陆失败handler
        .authenticationFailureHandler(jsonServerAuthenticationFailureHandler)
        // 无访问权限handler
        .authenticationEntryPoint(serverAuthenticationEntryPoint)
        .and()
        .logout()
        // 登出成功handler
        .logoutSuccessHandler(jsonServerLogoutSuccessHandler)
        .and()
        .csrf().disable()
        .httpBasic().disable()
        .authorizeExchange()
        // 白名单放行
        .pathMatchers(AUTH_WHITELIST).permitAll()
        // 访问权限控制
        .anyExchange().access(authorizeConfigManager).and().build();
@Bean
ReactiveAuthenticationManager reactiveAuthenticationManager() {
    LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
    managers.add(authenticationManager);
    return new DelegatingReactiveAuthenticationManager(managers);
}

Api请求校验类

AuthorizeConfigManager
@Slf4j
@Component
public class AuthorizeConfigManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication,
                                             AuthorizationContext authorizationContext) {
        boolean check = false;
        ServerWebExchange exchange = authorizationContext.getExchange();
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        Authentication authentication1=JwtTokenUtils.getAuthenticationeFromToken(request);
        if (null != authentication1) {
            for (GrantedAuthority authority : authentication1.getAuthorities()) {
                if (antPathMatcher.match(authority.getAuthority(), path)) {
                    log.info(String.format("用户请求API校验通过,GrantedAuthority:{%s}  Path:{%s} ", authority.getAuthority(), path));
                    check = true;
                }
            }
        }
        return Mono.just(new AuthorizationDecision(check));
    }

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) {
        return check(authentication, object)
                .filter(d -> d.isGranted())
                .switchIfEmpty(Mono.defer(() -> {

                    String body = ErrorCode.SYSTEM_ERROR.toString();
                    return Mono.error(new AccessDeniedException(body));
                }))
                .flatMap(d -> Mono.empty());
    }
}

登录成功类

JsonServerAuthenticationSuccessHandler
@Component
public class JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {

    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
        // 登录成功后可以放入一些参数到session中
        ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
        response.setStatusCode(HttpStatus.OK);
        // 系统登录认证
        String token = JwtTokenUtils.generateToken(authentication);
        response.getHeaders().set("token",token);



        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");

        DataBuffer buffer = response.bufferFactory().wrap(
                ResultDataUtils.getResponse(ErrorCode.SUCCESS.getCode(),ErrorCode.SUCCESS.getMessage(),authentication.getAuthorities()).getBytes(CharsetUtil.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}

获取用户信息类

public class UserInfoUtil {


    public static final String SECRET = "xxx";

        //用于子服务获取用户信息 
    public static String getUserName() throws GlobalException {

        return getUserName(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("token"));
    }

//用于gateway获取用户信息
    public static String getUserName(String token) throws GlobalException {

        if (token != null) {
            if (isTokenExpired(token)) {
                throw new GlobalException(ErrorCode.EXPIRED);
            }
            Claims claims = getClaimsFromToken(token);
            if (claims == null) {
                return null;
            }
            String username = claims.getSubject();
            if (username == null) {
                return null;
            }
            return username;
        }
        return null;
    }

    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    public static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }


}

二、前端

调用配置

export const login = data => axiosRequest.formdata('/login', data)

export function formdata(url, params = {}) {
    return new Promise((resolve, reject) => {
        httpService({
            url: url,
            method: 'post',
            data: params,
            transformRequest: [
                function (data) {
                    let ret = ''
                    for (let it in data) {
                        ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
                    }
                    ret = ret.substring(0, ret.lastIndexOf('&'));
                    return ret
                }
            ],
            headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
        }).then(response => {
            resolve(response)
        }).catch(error => {
            reject(error)
        })
    })
}

关键代码就是上面这些,还有其他的代码,下回有空再补吧。有需要的童鞋也可以一起讨论。~~其实是有点懒~~不想贴了.....

;