spring-cloud、spring-security、oauth、jwt

Jwt 全称JSON Web Token,是无状态登录的一种实现方式。什么是无状态,就是服务端无状态,即无session。用户的登录状态由客户端保存,每次请求携带token,然后解析。多了不说,想了解更多的可以看https://www.jianshu.com/p/576dbf44b2ae,这里介绍的很详细

先看看项目结构:

├─auth-server        
│  ├─src
│  │  ├─main
│            ├─java
│            │  └─com
│            │      └─lhf
│            │          └─authserver
│            │              │  AuthServerApplication.java
│            │              │  
│            │              └─config
│            │                      AuthServerConfig.java
│            │                      SecurityConfig.java
│            │                      
│            └─resources
│                │  application.properties
│                │  
│                ├─static
│                └─templates                     
│                          
├─resource-server         
│  ├─src
│  │  ├─main
│            ├─java
│            │  └─com
│            │      └─lhf
│            │          └─resourceserver
│            │              │  ResourceServerApplication.java
│            │              │  
│            │              ├─config
│            │              │      ResourceServerConfig.java
│            │              │      
│            │              └─controller
│            │                      TestController.java
│            │                      
│            └─resources
│                │  application.yml
│                │  
│                ├─static
│                └─templates                                                  
└─test        
    ├─src
    │  ├─main
            ├─java
            │  └─com
            │      └─lhf
            │          └─test
            │              │  TestApplication.java
            │              │  
            │              └─controller
            │                      TestController.java
            │                      
            └─resources
                │  application.yml
                │  
                ├─static
                └─templates
                        index.html                        

可以看到,和上篇文章的结构一样。

一、搭建授权服务器

添加maven节点
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

添加SecurityConfig.java

package com.lhf.authserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/11
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 密码比对器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 用户配置
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder().encode("123")).roles("admin");
    }

    /**
     * 安全配置,这里只是开启表单登录,不做其他配置,将交给资源服务器配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().formLogin();
    }
}

添加AuthServerConfig.java

package com.lhf.authserver.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import javax.annotation.Resource;
import java.util.Arrays;

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/11
 */
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    private ClientDetailsService clientDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 授权服务器安全配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()").allowFormAuthenticationForClients();
    }

    /**
     * 授权服务客户端配置
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("novel")
                .secret(passwordEncoder.encode("123"))
                .autoApprove(true)
                .resourceIds("lhf")
                .scopes("all")
                .redirectUris("http://localhost:8082/index.html")
                .authorizedGrantTypes("authorization_code");
    }

    /**
     * 授权服务器节点配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenServices(defaultTokenServices());
    }

    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices bean = new DefaultTokenServices();
        bean.setTokenStore(jwtTokenStore());
        bean.setSupportRefreshToken(true);
        bean.setAccessTokenValiditySeconds(60 * 60);
        bean.setRefreshTokenValiditySeconds(30 * 60);
        bean.setClientDetailsService(clientDetailsService);
        TokenEnhancerChain chain = new TokenEnhancerChain();
        chain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter()));//将token的增强方式注入进去
        bean.setTokenEnhancer(chain);
        return bean;
    }

    /**
     * token保存方式使用jwt
     * @return
     */
    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * jwt配置
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter bean = new JwtAccessTokenConverter();
        bean.setSigningKey("zjs");
        return bean;
    }
}

和普通的方式没多大的差别,只是TokenStore的实现方式不同

二、搭建资源服务

maven节点和授权服务一样

package com.lhf.resourceserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/11
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Bean
    public RemoteTokenServices remoteTokenServices() {
        RemoteTokenServices bean = new RemoteTokenServices();
        bean.setClientId("novel");
        bean.setClientSecret("123");
        bean.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        return bean;
    }

    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter bean = new JwtAccessTokenConverter();
        bean.setSigningKey("zjs");
        return bean;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenServices(remoteTokenServices()).resourceId("lhf").tokenStore(jwtTokenStore());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
    }

}

同样的,资源服务器也得添加token的实现了,因为不再是默认的配置了。

添加测试接口

package com.lhf.resourceserver.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/11
 */
@RestController
public class TestController {

    @GetMapping("hello")
    public String hello() {
        return "hello";
    }
}

三、测试服务

添加maven节点

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

添加登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>第三方登录</title>
</head>
<body>
<a href="http://localhost:8080/oauth/authorize?client_id=novel&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html">点击登录</a>
<h2 th:text="${msg}"></h2>
</body>
</html>

添加控制层

package com.lhf.test.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/11
 */
@Controller
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("index.html")
    public Object index(ModelAndView mv, String code) {

        mv.setViewName("index");
        if (code != null) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("code", code);
            map.add("client_id", "novel");
            map.add("client_secret", "123");
            map.add("redirect_uri", "http://localhost:8082/index.html");
            map.add("grant_type", "authorization_code");
            Map<String, String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
            String access_token = resp.get("access_token");
            System.out.println(access_token);
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "Bearer " + access_token);
            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
            ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/hello", HttpMethod.GET, httpEntity, String.class);
            mv.addObject("msg", entity.getBody());
        }
        return mv;
    }
}

测试:

请求 http://localhost:8082/index.html

 点击登录

 输入用户名密码,登录

成功请求到资源服务

项目源码地址:

gitee: https://gitee.com/lhfas/security-jwt.git

github: https://github.com/Liuhuifa/security-jwt.git

原文地址:https://www.cnblogs.com/Tiandaochouqin1/p/12872713.html