当前位置:   article > 正文

12. spring security oauth2和 JWT (2刷)_将编码的header、编码的payload、secret rsa256加密

将编码的header、编码的payload、secret rsa256加密

JWT

json web token

紧凑 且自包含的标准

HMAC算法 或 RSA(非对称加密)对 JWT进行签名

  • 紧凑形
  • 自包含

JWT 构成,3部分, 分别 . 分隔

  • Header
  • Payload 有效荷载
  • Signature 签名

xx.yy.zzz

header

两部分构成:令牌类型 和 使用的算法类型

令牌类型:HMAC,SHA256,RSA

{
"alg":"HS256",
"typ":"JWT"
}

  • 1
  • 2
  • 3
  • 4
  • 5

使用 Base64

Payload

用户信息 和 Claim (声明,权利)

有三种类型的 Claim : 保留,公开,和 私人

{
"sub":"12321321",
"name":"John Doe",
"admin":true
}
  • 1
  • 2
  • 3
  • 4
  • 5

同样用 Base64 编码

Signature

将 base64 编码后的 header,payload 和 秘钥 进行签名

HMACSHA256(
	base64UrlEncode(header) + "." +
	base64UrlEncode(payload),
	secret
)
  • 1
  • 2
  • 3
  • 4
  • 5

JWT认证流程

  1. 浏览器登录 ——

  2. 服务器用 秘钥创建 JWT,返回给浏览器

  3. 浏览器在 header加 JWT,请求接口

  4. 服务器获取JWT 解密,处理逻辑,响应

  • 架构流程
    1. 浏览器(用户名 密码)登录 到user-service
    2. user-service 用 client+用户名+密码,请求 auth-service,获取token
    3. auth-service ,确认信息无误,返回token
    4. user-service和 auth-service 注册同一个 eureka

eureka注意点

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

# 这种简单
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    serviceUrl:
      defaultZone: http://localhost:${server.port}/eureka/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

1. 编写 Uaa授权服务

User Account and Authentication 用户帐号及认证

pom 和 yaml

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
		</dependency>
web eureka-client jpa mysql-java
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
spring:
  application:
    name: uaa-service

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    username: root
    password: 123456
#都是 毫无亮点, driver-class-name url username password
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
server:
  port: 9999
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

配置 spring security,继承: Web Security Configurer Adapter

@Configuration
class WebSecurityConfig extends Web Security Configurer Adapter {

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) //401
            
            .and()
                .authorizeRequests() //所有请求,都要认证
                .antMatchers("/**").authenticated()
            
            .and()
                .httpBasic();
    }

    @Autowired
    UserServiceDetail userServiceDetail; //user Service Detail
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceDetail)
                .passwordEncoder(new BCryptPasswordEncoder());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

user Service Detail 和 Dao等都不变


@Service
public class UserServiceDetail implements UserDetailsService {

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



public interface UserDao extends JpaRepository<User, Long> {

	User findByUsername(String username);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

配置 Authorization Server

@Configuration
@EnableAuthorizationServer //开启 授权server
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            
                .withClient("user-service") //client secret scopes 权限类型
                .secret("123456")
                .scopes("service")
                .autoApprove(true)
            
                .authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code") //都支持了
            
                .accessTokenValiditySeconds(12 * 300);//5min过期
    }
    
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

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


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//JwtToken存储,解析器 也是 Jwt
        endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()).authenticationManager(authenticationManager);
        
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients().passwordEncoder(NoOpPasswordEncoder.getInstance());

        /**
         * 必须设置allowFormAuthenticationForClients 否则没有办法用postman获取token
         * 也需要指定密码加密方式BCryptPasswordEncoder
         */
    }

	//读取jks 文件
    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("fzp-jwt.jks"), "fzp123".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //这里测试后,作者的 文件可能已经失效,重新生成的 可用。 clean 在package
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("fzp-jwt"));
        return converter;
    } 
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
OAuth2的四种方式

模式
授权码(authorization-code)
隐藏式(implicit)
密码式(password)
客户端凭证(client credentials)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2. 生成 jks文件

  • java keytool 生成的
 keytool -genkeypair -alias fzp-jwt -validity 3650 -keyalg RSA -dname "CN=jwt,OU=jtw,O=jtw,L=zurich,S=zurich,C=CH" -keypass fzp123 -keystore fzp-jwt.jks -storepass fzp123
  • 1

alias 为别名

keypass storepass 为密码选项

validity 配置jks 文件的过期时间,天

获取 jks文件的公钥

 keytool -list -rfc --keystore fzp-jwt.jks | openssl x509 -inform pem -pubkey
  • 1

openssl 需要安装,不巡行这句,可以。输入密码 fzp123

命名为:public.cert,复制到 user-service(非uaa-servicce)的项目下。linux运行可行

certificate 
英 /səˈtɪfɪkət/  美 /sərˈtɪfɪkət/  全球(英国)  
简明 牛津 新牛津  韦氏  柯林斯 例句  百科
n. 证书;文凭,合格证书;电影放映许可证
v. 发给证明书,用证书证明
复数 certificates第三人称单数 certificates现在分词 certificating过去式 certificated过去分词 certificated
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

uua-service 放 fzp-jwt.jks pom增加

  • 项目中 真实是在 有public.cert的 项目增加的
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<configuration>
					<nonFilteredFileExtensions>
						<nonFilteredFileExtension>cert</nonFilteredFileExtension>
						<nonFilteredFileExtension>jks</nonFilteredFileExtension>
					</nonFilteredFileExtensions>
				</configuration>
			</plugin>
		</plugins>
	</build>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3. user-service资源服务

引入pom 和 配置 yaml


		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
		</dependency>

			openfeign hystrix eureka-client mysql-connector jpa web

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
server.port: 9090

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: user-service

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    username: root
    password: 123456

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

feign:
  hystrix:
   enabled: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  server port: 9090
  eureka
  application name
  mysql
  jpa

feign:
  hystrix:
   enabled: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

配置 Reource Server

@Configuration
public class JwtConfig {
    //jwt转换器
    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;

    //token 存储
    @Bean
    @Qualifier("tokenStore")
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
	
    //jwt token en hancer
    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        
        // jwt
        JwtAccessTokenConverter converter =  new JwtAccessTokenConverter();
        // 创建 resource
        Resource resource = new ClassPathResource("public.cert");
        String publicKey ;
        try {
            // 获得公钥, 通过 File CopyUtils.copyToByteArray,返回byte[]
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
		//公钥 复制给 jwt转换器,然后返回
        converter.setVerifierKey(publicKey);
        return converter;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
    Logger log = LoggerFactory.getLogger(ResourceServerConfig.class);

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests() //登录和注册 无需权限
                .antMatchers("/user/login","/user/register").permitAll()
                .antMatchers("/**").authenticated();
    }


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("foo").tokenStore(tokenStore);
    }

    @Autowired
    TokenStore tokenStore;

    @Autowired
    JwtAccessTokenConverter tokenConverter;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 配置 security
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig {

}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Service 和 Dao 不变

public interface UserDao extends JpaRepository<User, Long> {

	User findByUsername(String username);

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@Service
public class UserServiceDetail implements UserDetailsService {
	
    @Autowired
    private UserDao userRepository;
    
    @Autowired //权限 服务 client ,就是下面自对应的 feing
    AuthServiceClient client;
    
    @Override //查看用户名的不变
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
	
	//插入用户,用户名 密码
    public User insertUser(String username,String  password){
        User user=new User();
        user.setUsername(username);
        //密码 存储,使用 加密的
        user.setPassword(BPwdEncoderUtil.BCryptPassword(password));
        return userRepository.save(user);
    }
	
    //登录用户
    public UserLoginDTO login(String username,String password){
        //查询出用户
        User user=userRepository.findByUsername(username);
        if (null == user) {
          throw new UserLoginException("error username");
        }
        //如果密码 不匹配,就扔异常。 参数的 password是明文
        if(!BPwdEncoderUtil.matches(password,user.getPassword())){
            throw new UserLoginException("error password");
        }
        // 获取token,从 那个 hi-service的 basic 64中获取
        JWT jwt=client.getToken("Basic dXNlci1zZXJ2aWNlOjEyMzQ1Ng==","password",username,password);
        // 获得用户菜单
        if(jwt==null){
            throw new UserLoginException("error internal");
        }
        //设置上Jwt
        UserLoginDTO userLoginDTO=new UserLoginDTO();
        userLoginDTO.setJwt(jwt);
        userLoginDTO.setUser(user);
        return userLoginDTO;

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

密码工具类 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);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

controller



@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserServiceDetail userServiceDetail;

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

    @PostMapping("/login")
    public UserLoginDTO login(@RequestParam("username") String username , @RequestParam("password") String password){
        //参数判断,省略
        return userServiceDetail.login(username,password);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

@RestController
@RequestMapping("/foo")
public class WebController {

    @RequestMapping(method = RequestMethod.GET)
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    public String getFoo() {
        return "i'm foo, " + UUID.randomUUID().toString();
    }
}


@Component
public class AuthServiceHystrix implements AuthServiceClient {
    @Override
    public JWT getToken(String authorization, String type, String username, String password) {
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

用户登录接口 其他

feign开启

feign接口
@FeignClient(value = "uaa-service",fallback =AuthServiceHystrix.class )
public interface AuthServiceClient {
	//一个 Header请求。 3个参数请求
    @PostMapping(value = "/oauth/token")
    JWT getToken(@RequestHeader(value = "Authorization") String authorization,
                 @RequestParam("grant_type") String type,
                 @RequestParam("username") String username, 
                 @RequestParam("password") String password);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
JWT 和 UserLoginDto

public class UserLoginDTO {

    private JWT jwt;

    private User user;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
Exeption
@ControllerAdvice //切面
@ResponseBody //json 返回
public class ExceptionHandle {
    //拦截 这个自定义异常
    @ExceptionHandler(UserLoginException.class)
    public ResponseEntity<String> handleException(Exception e) {
		//返回 实体
        return new ResponseEntity(e.getMessage(), HttpStatus.OK);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
//继承:RuntimeException
public class UserLoginException extends RuntimeException{

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

进行测试

注册

http://localhost:9090/user/register?username=miyaaa&password=123456

{
    "id": 19,
    "username": "miyaaa",
    "password": "$2a$10$jMPZ6vBpKlIOpnSzUAeHXOmVqSERY6lkyLTZ95SuQc6t9NYlwrlzi",
    "authorities": null,
    "enabled": true,
    "credentialsNonExpired": true,
    "accountNonExpired": true,
    "accountNonLocked": true
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
登录
http://localhost:9090/user/login?username=miya6&password=123456
  • 1

{
    "jwt": {
        "access_token": "eyJhbGciOiJSUzIJ",
        "token_type": "bearer",
        "refresh_token": "eyJdafsadfds",
        "expires_in": 3599,
        "scope": "service",
        "jti": "36da2e6b-98aa-42f5-a51a-b951ce7d0179"
    },
    "user": {
        "id": 21,
        "username": "miya6",
        "password": "$2a$10$3Vw8tvtgeu7i9JYuNBiaP.U6UBV5AKkYotTWKHW.Q09IvzFbHnMvC",
        "authorities": [],
        "enabled": true,
        "accountNonLocked": true,
        "credentialsNonExpired": true,
        "accountNonExpired": true
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
http://localhost:9999/auth/token?grant_type=password&username=miya6&password=123456
很遗憾,这个接口 后台打一个日志,返回为空。但是feign就feign通了
header是
Authorization 
Basic dXNlci1zZXJ2aWNlOjEyMzQ1Ng==
  • 1
  • 2
  • 3
  • 4
  • 5

权限测试

http://localhost:9090/foo

Authorization
Bearer eyJhbGciOiJSUz ,如果没加 damin权限 不能访问。 库里加了 权限后,重新获取 token,即可访问

i'm foo, 17a22db1-834f-419b-b4b5-0607d7bdfcfc //UUID
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/83397
推荐阅读
相关标签
  

闽ICP备14008679号