SpringCloud-OAuth2.0-JWT 认证授权(密码模式,FeignClient远程调用获取jwt,实现单点登陆)

OAuth2.0 中包括认证服务器,和资源服务器,上一篇中介绍了认证服务器的配置,以及授权码模式,如何获取授权码,通过授权码,获取jwt令牌,

那么今天来看,密码模式,在密码模式中不再需要获取授权码这一步,通过调用认证服务器提供的/oauth/token接口,传参数:客户端信息(经过Base64编码,格式为 clientId:密钥 进行编码), grant_type 授权类型,以及用户名,密码,就可以获取jwt令牌

此案例中使用上一篇的认证服务配置,做少许改动,

然后就是新加一个登陆服务器,新建一个springboot项目,用作用户登陆,
登陆成功后,通过 SpringCloud FeignClient远程调用认证服务器/oauth/token,提交客户端id和密钥(经过Base64编码) grant_type 授权类型,还有用户账号密码,获取jwt令牌以及用户信息

后续客户端带token即可,资源服务进行验证(这一部分,后续在更新),

注意!!!
这里我只贴资源服务的代码了,后面会贴上认证服务器新加的配置

springcloud版本 <spring-cloud.version>Greenwich.BUILD-SNAPSHOT</spring-cloud.version>
1. pom文件核心依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</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>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.0.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.5</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

    </dependencies>

1.在资源服务中,需要ResouceServerConfig 去 extends ResourceServerConfigurerAdapter
此配置类中配置了spring-serurity的安全策略,

还有对令牌的验证,(此案例目前只是通过密码模式从认证服务器获取jwt令牌,后续如果客户端通过令牌访问资源,那么可能就考虑要验证jwt令牌,后续慢慢更新)

直接看代码

package com.youdu.resources.service.config;

import org.springframework.beans.factory.annotation.Autowired;
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.TokenStore;

/**
 * @author Sir_小三
 * @date 2020/1/31--11:39
 */
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {

    public static final String RESOURCE_ID="res1";

    @Autowired
    private TokenStore tokenStore;

    /**
     * 资源服务设置tokenStore,服务自己验证 ,如果使用对称加密,使用相同密钥
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(RESOURCE_ID);//资源id
        resources.tokenStore(tokenStore);
    }

    /**
     * login 和 register 不需要认证。其他请求都需要进行认证
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .antMatchers("/login","/register").permitAll()
                .antMatchers("/**").authenticated();
    }
}

4.JwtTokenConfig 配置类,认证服务器使用对称加密,资源服务器校验jwt令牌也要使用相同的密钥,(不过对于此案例,目前只是通过密码模式从认证服务器获取jwt令牌,所以此配置也影响不大,不过后续肯定是需要的)

package com.youdu.resources.service.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**资源服务和认证服务使用相同对称加密进行解析令牌
 * @author Sir_小三
 * @date 2020/1/30--14:34
 */

@Configuration
public class TokenConfig {


    //对称加密,密钥,也可采用非对称加密
    private  String SIGNING_KRY="lyj123";
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KRY);
        return converter;
    }

}

5.还有一个重要的,UserServiceDetail 配置
这里SecurityUser 去继承security.core.userdetails.User提供的user
此类来验证登陆用户密码

(这里的UserServiceDetail,和SecurityUser )类不变,跟上一篇认证服务器的配置一致

package com.youdu.distributed.authentication.service;

import com.youdu.distributed.authentication.entity.SecurityUser;
import com.youdu.distributed.authentication.entity.TbUser;
import com.youdu.distributed.authentication.mapper.TbUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Sir_小三
 * @date 2020/1/30--12:54
 */
@Service
public class UserServiceDetail implements UserDetailsService {

    @Autowired
    private TbUserMapper tbUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        TbUser tbUser = new TbUser();
        tbUser.setName(username);
        List<TbUser> page = tbUserMapper.page(tbUser, null);
        TbUser tbUser1 = page.get(0);
        SecurityUser securityUser = new SecurityUser(username, tbUser1.getPassword(),grantedAuthorities);
        return securityUser;
    }
}

package com.youdu.distributed.authentication.entity;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

/**
 * @author Sir_小三
 * @date 2020/1/30--18:56
 */
public class SecurityUser extends User {

    public SecurityUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public SecurityUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }
}

主要看下登陆接口,提供注册用户,和登陆用户两个接口。注册用户就是添加用户,密码进行加密。
登陆接口,首先判断账号密码,若ok,则远程调用认证服务器,获取jwt令牌,最后在封装用户信息,一起返回

package com.youdu.resources.service.controller;
import com.youdu.resources.service.entity.TbUser;
import com.youdu.resources.service.entity.UserLoginDTO;
import com.youdu.resources.service.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/register")
    public TbUser postUser(@RequestParam("username") String username, @RequestParam("password") String password) {
        /* 参数判断,省略 */
        return userService.insertUser(username, password);
    }

    @PostMapping("/login")
    public UserLoginDTO login(@RequestParam("username") String username, @RequestParam("password") String password) {
        //参数判断,省略
        return userService.login(username, password);
    }

    @GetMapping("/test")
    public String test() {

        return "test---api";
    }
}

这里是service代码
调用认证服务器的时候,这里需要将客户端id和密钥进行编码,第二个参数为授权类型,后面两个为用户账号密码
(个人理解对于访问认证服务器进行认证的,都属于客户端,那么也就应该都配置在oauth_client_details表中,客户端需要获取令牌,那就带着自己的身份来,认证服务器从表中查询,有没有这号人,有发放令牌,没有那就对不起了,拜拜)

    JWT jwt=client.getToken("Basic cmVzb3VyY2VzLXNlcnZpY2U6MTIzNDU2","password",username,password);
package com.youdu.resources.service.service;

import com.youdu.resources.service.client.AuthServiceClient;
import com.youdu.resources.service.entity.JWT;
import com.youdu.resources.service.entity.TbUser;
import com.youdu.resources.service.entity.UserLoginDTO;
import com.youdu.resources.service.exception.UserLoginException;
import com.youdu.resources.service.mapper.TbUserMapper;
import com.youdu.resources.service.utils.BPwdEncoderUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * @author Sir_小三
 * @date 2020/1/31--16:40
 */
@Service
public class UserService {
    @Autowired
    private AuthServiceClient client;
    @Autowired
    private TbUserMapper tbUserMapper;

    public TbUser insertUser(String username, String password) {
        TbUser user=new TbUser();
        user.setName(username);
        user.setPassword(BPwdEncoderUtil.BCryptPassword(password));
        tbUserMapper.insert(user);
        return user;
    }

    public UserLoginDTO login(String username, String password) {
        TbUser user=new TbUser();
        user.setName(username);
        List<TbUser> page = tbUserMapper.page(user, null);
        TbUser tbUser = page.get(0);
        if (null == tbUser) {
            throw new UserLoginException("error username");
        }
        if(!BPwdEncoderUtil.matches(password,tbUser.getPassword())){
            throw new UserLoginException("error password");
        }
        // 获取token
        JWT jwt=client.getToken("Basic cmVzb3VyY2VzLXNlcnZpY2U6MTIzNDU2","password",username,password);
        // 获得用户菜单
        if(jwt==null){
            throw new UserLoginException("error internal");
        }
        UserLoginDTO userLoginDTO=new UserLoginDTO();
        userLoginDTO.setJwt(jwt);
        userLoginDTO.setUser(tbUser);
        return userLoginDTO;
    }
}

这里面用到两个类JWT和UserLoginDTO,我都贴上来
省略getset方法

package com.youdu.resources.service.entity;


public class JWT {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private int expires_in;
    private String scope;
    private String jti;

此类中两个属性,JWT令牌,以及user 用户信息,最终返回这个类给客户端

package com.youdu.resources.service.entity;


public class UserLoginDTO {

    private JWT jwt;

    private TbUser user;

    public JWT getJwt() {
        return jwt;
    }

    public void setJwt(JWT jwt) {
        this.jwt = jwt;
    }

    public TbUser getUser() {
        return user;
    }

    public void setUser(TbUser user) {
        this.user = user;
    }
}

还有一个BPwdEncoderUtil工具类,加密密码,和验证密码,传入密码,和数据库比较

package com.youdu.resources.service.utils;


import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


public class BPwdEncoderUtil {

    private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    /**
     * 用BCryptPasswordEncoder
     * @param password
     * @return
     */
    public static String  BCryptPassword(String password){
        return encoder.encode(password);
    }

    /**
     *
     * @param rawPassword
     * @param encodedPassword
     * @return
     */
    public static boolean matches(CharSequence rawPassword, String encodedPassword){
        return encoder.matches(rawPassword,encodedPassword);
    }

}

声明FeignClient客户端

package com.youdu.resources.service.client;

import com.youdu.resources.service.entity.JWT;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author Sir_小三
 * @date 2020/1/31--13:52
 */
@FeignClient("distributed-authentication")
public interface AuthServiceClient {
    @PostMapping("/oauth/token")
    JWT getToken(@RequestHeader("Authorization") String authorization,
                 @RequestParam("grant_type")String type,
                 @RequestParam("username") String username,
                 @RequestParam("password") String password
    );
}

全局异常处理也贴一下吧

package com.youdu.resources.service.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;


@ControllerAdvice
@ResponseBody
public class ExceptionHandle {
    @ExceptionHandler(UserLoginException.class)
    public ResponseEntity<String> handleException(Exception e) {

        return new ResponseEntity(e.getMessage(), HttpStatus.OK);
    }
}

package com.youdu.resources.service.exception;

public class UserLoginException extends RuntimeException{

    public UserLoginException(String message) {
        super(message);
    }

}

**=====================================================**

接下来补上认证服务器补充的配置,在认证服务器SecurityConfig配置类中
添加认证管理器的bean,注意看注释

	/**
	 * 	//配置认证管理器(使用密码模式,必须要配置)
	 * 	注意!!!这里重写父类的authenticationManagerBean方法,方法名写错,栈溢出error,不能粗心
	 * @return
	 */
	@Override
	@Bean
	public AuthenticationManager authenticationManagerBean() throws Exception{
		return super.authenticationManagerBean();
	}

然后在AuthorizationServer配置类中注入AuthenticationManager管理器

    @Autowired
    private AuthenticationManager authenticationManager;

最后在这里配置AuthenticationManager

    /**
     * 配置令牌访问端点url,和令牌服务(令牌生成策略,如何生成)
     * /oauth/authorize   授权端点
     * 获取授权码
     * http://localhost:8762/oauth/authorize?response_type=code&client_id=resources-service&scope=all&client_secret=123456&redirect_uri=http://www.baidu.com
     * /oauth/token       获取令牌端点(发送post请求,获取token,code=申请的授权码)
     *
     * /oauth/confirm_access  用户确认授权端点
     * /oauth/error  授权服务错误信息端点
     * /oauth/check_token  用于资源服务访问的令牌解析端点
     * /oauth/token_key  提供公有密钥的端点,如果使用jwt令牌的话
     * 授权端点url应该被spring-security 保护起来,只供授权用户访问
     * @param endpoints
     * @throws Exception
     */
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)//认证管理服务,密码模式必须配置,还有userServiceDetail
                .authorizationCodeServices(authorizationCodeServices)//授权码模式
                .tokenStore(tokenStore)
                //在这里设置jwtAccessTokenConverter,发现是ok的,可以生成jwt令牌
                .accessTokenConverter(jwtAccessTokenConverter)
                //.userDetailsService(userServiceDetail)//密码模式要配
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

到这里核心代码就贴完了,可以调用注册接口,添加一个用户,然后进行登陆,获取jwt令牌以及用户信息

最后效果,返回令牌以及用户信息
在这里插入图片描述

11,有不清除的或者想要源码的,可加我微信,xiaosang953038659,谢谢你看到最后,嘻嘻

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值