SpringCloud从零构建(八)——基于OAuth2.0+JWT的认证中心(上——认证中心配置)

基于OAuth2.0+JWT的鉴权中心(上——认证中心配置)

上一讲中我们讲了项目的大门gateway的配置。我们一般的业务流程应该是用户访问服务,经过网关被拦截,然后通过鉴权中心进行认证,认证结束发放令牌,用户拿着令牌去访问资源,因此这一讲我们主要讨论一下鉴权认证。因为我用的是SpringCloud(2.1.6) 的Greenwich版本,所以我们本篇文章就是基于OAuth2.0+JWT,由于篇幅过长所以我分为两部分来讲,第一部分是认证中心配置我们也称为认证服务器,下一篇是资源中心配置,也叫资源服务器配置。

一)创建项目

创建模块主要分为两部分,认证服务器authorization-server模块和资源服务器app-customer-login模块。
A、authorization-server创建
这个模块主要是
1、创建一个新的模块,和前面几讲的一样,打开上一篇创建的项目microservice选中,并单击右键New→Module,选择Spring Initializr默认下一步;
2、进入该页面填Group和Artifact,Group要写对和上次的要一样,Artifact 我命名为authorization-server,如图1所示:
在这里插入图片描述
3、点击Next,选择相关依赖,Spring Cloud Security 下的Cloud OAuth2是核心依赖如下图2所示:
在这里插入图片描述
4、继续Next→Finish,等待下载依赖。完成后,首先进行启动类AuthorizationServerApplication的配置,如下图3所示:
在这里插入图片描述
@EnableResourceServer这个注解要注意,它是用来开启安全管控的,因为后面如果我们要继承WebSecurityConfigurerAdapter来完善更加详细的规则配置。
5、首先我们先建三个文件夹,分别为com.glen.authorizationserver.config、com.glen.authorizationserver.entity和com.glen.authorizationserver.service,如下图所示:
在这里插入图片描述
6、接下来我们配置AuthorizationServerApplication,其中@SpringBootApplication和@EnableDiscoveryClient这是基础注解,不再赘述。@EnableFeignClients这是在做负载均衡时讲过的。@EnableResourceServer这个注解比较特殊, 它用于开启资源服务配置,会配置资源服务相关的安全配置。
在这里插入图片描述
7、配置application.properties,如下图所示,具体信息看注释。
在这里插入图片描述

#微服务配置
spring.application.name=auth-server
server.port=8085
eureka.client.serviceUrl.defaultZone=http://localhost:8081/eureka/

# 数据库JPA配置
# Mysql驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据库地址  serverTimezone=UTC要写,不然有时会报错
spring.datasource.url=jdbc:mysql://localhost:3306/springauth?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
# 用户名
spring.datasource.username=root
# 密码
spring.datasource.password=glen1996
# 每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新
spring.jpa.hibernate.ddl-auto=update
# 是否打印sql语句
spring.jpa.show-sql=true

# 当遇到同样名字的时候,是否允许覆盖注册
spring.main.allow-bean-definition-overriding=true
# security用户名
spring.security.user.name=glen
# security密码
spring.security.user.password=123456
# 鉴权配置 配置token获取合验证时的策略
security.oauth2.authorization.check-token-access=permitAll()
# 暴露地址
management.endpoints.web.exposure.include=*

8、在com.glen.authorizationserver.entity下创建Role.java和User.java,这两个文件主要是角色和用户信息。
Role.java中的内容:

@Entity
public class Role implements GrantedAuthority {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String getAuthority() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

User.java中的内容:

@Entity
public class User implements UserDetails, Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false,  unique = true)
    private String username;

    @Column
    private String password;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private List<Role> authorities;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Role> authorities) {
        this.authorities = authorities;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

9、在com.glen.authorizationserver.java中创建UserRepository.java和UserServiceDetail.java两个文件。
UserRepository.java
UserRepository这个类继承了JpaRepository,这个类封装了很多方法,jpa和mybatis这一点有点类似,如果有用过Mybatis的同学就会比较熟,项目中的findByUsername是自定义的方法,具体可以查看jpa的官方文档,这个方法主要是用来做查询,它和数据库的user表对应,后面我们会具体讨论数据库的问题。

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

UserServiceDetail.java
UserServiceDetail实现了UserDetailsService 这个接口的方法,返回了查询结果。

@Service
public class UserServiceDetail implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}

10、接下来我们要配置认证服务器,这一块是核心内容。新建两个java文件。OAuth2Config.java和WebSecurityConfig.java
A、OAuth2Config.java

OAuth2Config这个class主要是配置客户端信息和认证信息,认证信息主要是包含了JWT的配置,JWT个人感觉还是比session要好很多,JWT最大的优势还是在于通过无状态、可扩展的方式处理用户请求以及不用访问数据库去获取凭证,咱也可以不用redis。
JWT主要分为三部分Header(进行编码,用于指定加密算法)、Payload(进行编码)和Signature(即签名,它由编码的header和payload,使用用户指定的密钥secret,采用header中指定的哈希算法生成)

@Slf4j
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    @Qualifier("authenticationManagerBean")
    AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private UserServiceDetail userServiceDetail;
    @Autowired
    private ClientDetailsService clientDetailsService;


    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

    @Bean
    public JwtAccessTokenConverter jwtTokenEnhancer() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("test-jwt.jks"), "test123".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("test-jwt"));
//      converter.setSigningKey("123");
        log.info("private key:"+converter);
        return converter;
    }

    @Bean // 声明 ClientDetails实现
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
       String finalSecret =new BCryptPasswordEncoder().encode("123456");
        clients.inMemory()
                // 配置一个客户端
                .withClient("user-service")
                .secret("123456")
                // 配置客户端的域
                .scopes("service")
                // 配置验证类型为refresh_token和password
                .authorizedGrantTypes("refresh_token","password")
                // 配置token的过期时间为1h
                .accessTokenValiditySeconds(3600 * 1000)
                .resourceIds("user-service")
                .authorities("ROLE_ADMIN");

    //  clients.withClientDetails(clientDetailsService);

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置token的存储方式为JwtTokenStore
        endpoints
                 //存储jwt
                .tokenStore(tokenStore())
                // 配置用于JWT私钥加密的增强器
                .tokenEnhancer(jwtTokenEnhancer())
                // 配置安全认证管理
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许表单认证
        security.tokenKeyAccess("permitAll()") //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
                .checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
                .allowFormAuthenticationForClients();
    }
}

以下是相关的方法说明:
a、tokenStore():tokenStore方法返回一个TokenStore对象的子对象JwtTokenStore,认证服务器取值给授权服务器配置器,通俗点就是让MyAuthorizationServerConfig能注入到值。
b、jwtTokenEnhancer():这个方法主要是用来设置认证服务器和资源服务器之间的密钥对,常见的有对称加密和非对称加密两种。converter.setSigningKey(“123”)这是对称加密方法,如果这里这样设置,资源服务器里也需要这样设置; converter.setKeyPair()这是非对称的加密方法。这一块要注意两个问题:
(1)、生成用于Token加密的私钥文件test-jwt.jks。jks文件的生成我们一般使用java自带的java keytool工具,只要环境变量配置没问题,输入如下命令:

keytool -genkeypair -alias test-jwt -validity 3650 -keyalg RSA -dname "CN=jwt,OU=jtw,O=jtw,L=zurich,S=zurich,C=CH" -keypass test123 -keystore test-jwt.jks -storepass test123</font>

回车即可,
-keypass test123:这是设置别名
-keystore test-jwt.jks:这是设置文件名
-storepass test123:这是设置密码,
如下图所示:
在这里插入图片描述
表示已经成功,在如图当前目录下即可查到该文件。
在这里插入图片描述
并将该文件放在resources目录下面
在这里插入图片描述
接下来我们运行如下命令,输入密码test123获取publicKey,这个后面要用:

keytool -list -rfc --keystore test-jwt.jks | openssl x509 -inform pem -pubkey

在这里插入图片描述
新建一个txt文件命名为public.cert,我们复制里面的publicKey到该文件中。

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsTuQdiX2yhgNLLHgUxjc
zher2JC63l3/DQAdRq/TX9harNie25KOZua9/CF+9XKNjKlJsecCj/UUo6ICAiTB
lOxbLjj/bIoIMefn/xtQNtPMPcZr5XdMmZRHW2kHg6y5yGDEYXWFzEtqXrULfUMP
CwV6vSoqxi6xj6i9wQPdCOCZiBBxWZfOwir4oDN/kD0eulCHd9H1l1SBtdRDGFeE
QBUjFMrDhurXUjYWay3021bGU2SbVUS1Av+qvpSmEQ7pRxf18QOtKD5Z2yP3EdS0
0jme/K84DDqbyUo3Zboh4YFz8OIXNrtjXFgPd3MrotTzlBr2teR4twF4zd01voQr
wQIDAQAB
-----END PUBLIC KEY-----

(2)、要在pom.xml文件中配置如下代码:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>cert</nonFilteredFileExtension>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>

目的是为了防止jks文件在maven编译时乱码,如果这个文出问题后面可能会导致无权限访问的问题。
c、clientDetailsService():
d、configure(ClientDetailsServiceConfigurer clients) :这个方法主要是配置客户端,具体看注解,重点说一下楼主遇到的坑,要设置一下.resourceIds(“user-service”)和.authorities(“ROLE_ADMIN”),不然后面访问的时候可能会报如下错误:

{
    "error": "access_denied",
    "error_description": "Invalid token does not contain resource id (oauth2-resource)"
}

resourceIds这个的值随便设总要要前后文上下文等等保持一致就行,数据库也一样,我这里设的的clientid一样只是为了方便……嗯,没错,只是为了方便……
e、configure(AuthorizationServerEndpointsConfigurer endpoints):这个方法主要是配置token的存储方式。
f、configure(AuthorizationServerSecurityConfigurer security) :这个是允许表单认证。
B、WebSecurityConfig.java
WebSecurityConfig类主要是继承WebSecurityConfigurerAdapter类,
代码如下:

@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/token","/oauth/**").permitAll();
    }

    @Autowired
    UserServiceDetail userServiceDetail;

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

下面对各个方法做下说明:
a、passwordEncoder():这个方法类似于格式化工具方法,因为spring security升级后有所改变。如果加密方式不对,很有可能报以下错误:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

b、authenticationManagerBean():
这个bean,在OAuth2Config文件中有引用到。
c、configure(HttpSecurity http):这里在配置权限,防止令牌获取不被通过,所以允许了oauth开头的所有路径。
d、configure(AuthenticationManagerBuilder auth):这个方法还是去数据库查询用户的密码,做权限验证。

好了本篇文章就讲到这里,下篇文章我们讲基于OAuth2.0+JWT的认证中心(下——资源中心配置)

SpringCloud从零构建(一)——Eureka注册中心
SpringCloud从零构建(二)——创建服务端Server
SpringCloud从零构建(三)——创建消费者Customer
SpringCloud从零构建(四)——Feign实现负载均衡
SpringCloud从零构建(五)——Config配置中心
SpringCloud从零构建(六)——消息总线Bus+Rabbit MQ实现动态刷新
SpringCloud从零构建(七)——网关中心(gateway)配置
SpringCloud从零构建(八)——基于OAuth2.0+JWT的鉴权中心(上——认证中心配置)

github地址:https://github.com/gjen1996/microservice
如果有问题欢迎小伙伴留言和我沟通交流。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值