授权验证

240 阅读6分钟

这是我参与「掘金日新计划 · 6 月更文挑战」的第29天 ,点击查看活动详情

一、创建项目

创建authcode-server文件夹,打开文件夹

二、创建资源服务器

1、创建授权服务器模块

创建Spring Boot模块:authcode-rs

2、配置pom

 

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.22</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3、配置application.properties

 

server.port=8080

4、创建实体类

 

package com.atguigu.authcoders.api;
@Data
public class UserInfo {
    private String name;
    private String email;
}

5、创建api

 

package com.atguigu.authcoders.api;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
    @RequestMapping("/api/userinfo")
    public ResponseEntity<UserInfo> getUserInfo() {
        String username = "helen";
        String email = username + "@atguigu.com";
        UserInfo userInfo = new UserInfo();
        userInfo.setName(username);
        userInfo.setEmail(email);
        return ResponseEntity.ok(userInfo);
    }
}

6、启动资源服务器

7、访问资源api

http://localhost:8080/api/userinfo\

得到资源数据

 

{"name":"helen","email":"helen@atguigu.com"}

三、加入Spring Cloud Security

1、配置pom

 

<dependencyManagement>
    <dependencies>
        <!--Spring Cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    
    ......
   
    <!--微服务安全-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
</dependencies>

2、修改api

 

package com.atguigu.authcoders.api;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
    @RequestMapping("/api/userinfo")
    public ResponseEntity<UserInfo> getUserInfo() {
        User user = (User) SecurityContextHolder
                .getContext()
                .getAuthentication()
                .getPrincipal();
        String username = user.getUsername();
        String email = username + "@atguigu.com";
        UserInfo userInfo = new UserInfo();
        userInfo.setName(username);
        userInfo.setEmail(email);
        return ResponseEntity.ok(userInfo);
    }
}

3、配置默认的用户名和密码

在application.properties中配置

 

spring.security.user.name=xiaolu
spring.security.user.password=xyz

4、重启资源服务器

\

5、访问资源api

出现登录页面:输入前面刚刚配置的用户名和密码可以访问api接口

\

四、配置资源服务器

1、添加配置文件

 

package com.atguigu.authcoders.config;
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;
/**
 * 资源服务配置
 */
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()//所有请求
            .authenticated()//都要进行OAuth2验证
            .and() //
            .requestMatchers() //缩小匹配范围:
            .antMatchers("/api/**");//对/api/*资源进行OAuth2验证
    }
}

2、重启资源服务器

\

3、访问资源api

提示未授权:必须使用access_token才能访问到资源

\

五、创建授权服务器

1、创建授权服务器模块

创建Spring Boot模块:authcode-as

2、配置pom

 

<dependencyManagement>
    <dependencies>
        <!--Spring Cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--微服务安全-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <!--oauth2-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3、配置application.properties

 

server.port=8081

4、添加OAuth2配置文件

 

package com.atguigu.authcodeas.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.provisioning.InMemoryUserDetailsManager;
/**
 * 授权服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {
    // 添加商户信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()//token等信息存入内存
            .withClient("clientapp")//客户端id
            .secret(passwordEncoder().encode("123456"))//客户端secret
            .redirectUris("http://localhost:8082/callback")//回调客户应用服务器
            // 授权码模式
            .authorizedGrantTypes("authorization_code")
            .scopes("read_userinfo", "read_contacts");//细分权限
    }
    // 设置添加用户信息,正常应该从数据库中读取
    @Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
                .authorities("ROLE_USER").build());
        userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
                .authorities("ROLE_USER").build());
        return userDetailsService;
    }
    @Bean  //password加密的方式 相当于把PasswordEncoder类对象 注册到容器中
    PasswordEncoder passwordEncoder() {
        // 加密方式
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder;
    }
}

5、添加Security配置文件

 

package com.atguigu.authcodeas.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.stereotype.Component;
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    // 授权中心管理器
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    // 拦截所有请求,使用httpBasic方式登录
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();  
    }
}

5、启动授权服务器

验证token 刷新token 都有日志\

\

6、获取授权码

请求认证服务获取授权码:

Get请求:

http://localhost:8081/oauth/authorize?response_type=code&client_id=clientapp&state=xyz&scope=read_userinfo&redirect_uri=http://localhost:8082/callback

\

参数列表如下:

  • response_type:授权码模式固定为code。\

  • client_id:客户端id,和授权配置类中设置的 withClient 一致。\

  • redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。 \

  • scope:客户端范围,和授权配置类中设置的scopes一致(可选)\

  • state:防止csrf攻击(可选)\

\

输入用户名和密码

 点击确认后: 选择是否接受资源授权?

\

选择接受:跳转到回调地址中,并且拿到授权码 ,框架自带了生成授权码的流程

六、申请令牌

1、使用postman发送post请求

(1)配置Basic Auth

(2)点击上图的“Preview Request”,在Headers中显示

(3)body中选择form-data,录入需要传递的参数,点击“send”,得到授权码

注意:不要反复使用一个code测试请求,否则会出现 invalid_token 错误

  • access_token:表示访问令牌,必选项。\

  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。\

  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。\

  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。\

  • scope:表示权限范围,如果与客户端申请的范围一致,可选项。\

\

七、生成JWT令牌

授权服务器中生成令牌:authcode_as

1、秘钥库文件

将 guli.keystore 放在resources目录下

2、配置文件中添加配置

 

encrypt.key-store.location=classpath:/guli.keystore
encrypt.key-store.secret=guli123store
encrypt.key-store.alias=gulikey
encrypt.key-store.password=guli123

3、用户对象转换器

将用户信息转换为map

 

package com.atguigu.authcodeas.config;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
    @Override
    public Map<String, Object> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new HashMap();
        Object principal = authentication.getPrincipal();
        UserDetails userDetails = (UserDetails) principal;
        response.put("name", userDetails.getUsername());
        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }
        return response;
    }
}

4、修改认证服务器配置文件

 

package com.atguigu.authcodeas.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.provider.token.DefaultAccessTokenConverter;
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;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import javax.annotation.Resource;
import java.security.KeyPair;
/**
 * 授权服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {
    // 添加商户信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()//token等信息存入内存
                .withClient("clientapp")//客户端id
                .secret(passwordEncoder().encode("123456"))//客户端secret
                .redirectUris("http://localhost:8082/callback")//回调客户应用服务器
                // 授权码模式
                .authorizedGrantTypes("authorization_code")
                .scopes("read_userinfo", "read_contacts");//细分权限
    }
    // 设置添加用户信息,正常应该从数据库中读取
    @Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
                .authorities("ROLE_USER").build());
        userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
                .authorities("ROLE_USER").build());
        return userDetailsService;
    }
    //注入用户信息
    @Autowired
    UserDetailsService userDetailsService;
    @Bean  //password加密的方式 相当于把PasswordEncoder类对象 注册到容器中
    PasswordEncoder passwordEncoder() {
        // 加密方式
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder;
    }
    //读取密钥的配置
    @Bean("keyProp")
    public KeyProperties keyProperties(){
        return new KeyProperties();
    }
    //注入密钥的配置
    @Resource(name = "keyProp")
    private KeyProperties keyProperties;
    //创建jwt令牌转换器
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //密钥工厂
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
                keyProperties.getKeyStore().getLocation(),
                keyProperties.getKeyStore().getSecret().toCharArray());
        //密钥对(公钥和私钥)
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(
                keyProperties.getKeyStore().getAlias(),
                keyProperties.getKeyStore().getPassword().toCharArray());
        converter.setKeyPair(keyPair);
        //配置自定义的CustomUserAuthenticationConverter
        DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
        return converter;
    }
    //注入jwt令牌转换器
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    //创建tokenStore
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    //注入tokenStore
    @Autowired
    TokenStore tokenStore;
    @Autowired
    AuthenticationManager authenticationManager;
    //授权服务器端点配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.accessTokenConverter(jwtAccessTokenConverter)
                .authenticationManager(authenticationManager)//认证管理器
                .tokenStore(tokenStore)//令牌存储
                .userDetailsService(userDetailsService);//用户信息service
    }
}

\

八、使用令牌访问资源

资源服务器中校验令牌:authcode_rs\

1、公钥文件

将 publickkey.pem 放在resources目录下

2、修改资源服务器配置文件

 

package com.atguigu.authcoders.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
/**
 * 资源服务配置
 */
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
    //公钥
    private static final String PUBLIC_KEY = "publickey.pem";
    /**
     * 获取非对称加密公钥 Key
     * @return 公钥 Key
     */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }
    //定义JwtAccessTokenConverter,使用jwt令牌
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }
    //定义JwtTokenStore,使用jwt令牌
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
//        http.csrf().disable();
        http.authorizeRequests()
            .anyRequest()//所有请求
            .authenticated()//都要进行OAuth2验证
            .and() //
            .requestMatchers() //缩小匹配范围:
            .antMatchers("/api/**");//对/api/*资源进行OAuth2验证
    }
}

3、新建测试接口

因为使用的并不是用户名密码的方式授权,所以/api/userinfo接口并不能获取出用户信息

新建一个获取课程信息的接口

 

@RequestMapping("/api/couseIfo")
public ResponseEntity<Map<String, Object>> getCourseInfo() {
    Map<String, Object> map = new HashMap<>();
    map.put("id", 1);
    map.put("title", "Spring Security OAuth2.0");
    map.put("price", 99);
    return ResponseEntity.ok(map);
}

4、重启资源服务器

5、使用postman测试