Spring Cloud Security[微服务安全](二)授

2020-06-09  本文已影响0人  ElliotG

0. 概述

根据之前的介绍,授权码模式要先获取授权码,然后再根据授权码获取token,最后根据token获取服务器端的资源。

 

1. 项目依赖

parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.7.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
</properties>

spring security和oauth关键依赖

<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.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

application.properties

server.port=8092
spring.application.name=oauth2-server

 

2. 添加用户服务来获取授权码

/**
 * Created by KG on 2019/9/30
 */
@Service
public class UserService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    private List<User> userList;

    @PostConstruct
    public void initData() {
        String password = passwordEncoder.encode("123456");
        userList = new ArrayList<>();
        userList.add(new User("kg", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")));
        userList.add(new User("kelvin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
        userList.add(new User("david", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<User> findUserList = userList.stream().filter(user -> user.getUsername().equals(username)).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(findUserList)) {
            return findUserList.get(0);
        } else {
            throw new UsernameNotFoundException("用户名或密码错误");
        }
    }
}

User

/**
 * Created by KG on 2019/8/29
 */
public class User implements UserDetails {

    private String username;
    private String password;
    private List<GrantedAuthority> authorities;

    public User(String username, String password,List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

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

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

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

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

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

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

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

代码解释:
由于是演示,我们使用了一个基于内存的硬编码的用户列表。

 

3. 配置认证服务器

/**
 * 认证服务器配置
 * Created by KG on 2019/9/30
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    /**
     * 使用用户服务来控制认证服务器的访问
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")//配置client_id
                .secret(passwordEncoder.encode("admin123456"))//配置client-secret
                .accessTokenValiditySeconds(3600)//配置访问token的有效期
                .refreshTokenValiditySeconds(864000)//配置刷新token的有效期
                .redirectUris("http://www.baidu.com")//配置redirect_uri,用于授权成功后跳转
                .scopes("all")//配置申请的权限范围
                .authorizedGrantTypes("authorization_code", "password");//配置grant_type,表示授权类型
    }
}

代码解释:
在endpoints的配置中,我们正好利用了之前编写的用户服务,用来控制对认证服务器的访问权限。
在第二个configure中我们配置了client_id和client_secrect,因为要获取token,光靠授权码还是不够的。

 

4. 安全路由配置

SecurityConfig

/**
 * SpringSecurity配置
 * Created by KG on 2019/10/8
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll();
    }
}

代码解释:
我们需要配置一些例外路由,例如oauth的还有登录和登出,它们都不需要在安全框架控制之内,否则就访问不了了。

 

5. 资源服务器配置

ResourceServerConfig

/**
 * 资源服务器配置
 * Created by KG on 2019/9/30
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/user/**");//配置需要保护的资源路径
    }
}

代码解释:
我们添加了一个需要保护的资源路由,/user/**,我们会在稍后加上这个资源的实现。

 

6. 保护资源实现

UserController

/**
 * Created by KG on 2019/9/30
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication.getPrincipal();
    }
}

 

7. 运行步骤1 - 获取认证服务器授权并获取授权码

授权操作 回调地址中获取授权码

授权码地址:
https://www.baidu.com/?code=LV2Qfu&state=normal

授权码:
LV2Qfu

 

8. 运行步骤2 - 通过client_id,client_secret, 授权码请求token

令牌返回结果:

{
    "access_token": "3a586c5d-a707-4ce0-8b0e-e789058f92f5",
    "token_type": "bearer",
    "expires_in": 3599,
    "scope": "all"
}

Token:
3a586c5d-a707-4ce0-8b0e-e789058f92f5

 

9. 运行步骤3 - 通过token访问资源

Bearer Token 自动生成请求头 资源返回结果

好了,一个简易的通过授权码和认证服务器来访问资源的模式就完成了。

上一篇 下一篇

猜你喜欢

热点阅读