Spring Security + Oauth2 认证授权

微服务系列

1、Nacus 服务搭建及使用
2、Nacos 配置中心
3、Nacos 服务注册与发现之OpenFeign服务间调用
4、Spring Security & Oauth2 认证授权
5、网关(Gateway)的搭建及使用
6、网关(Gateway自定义断言和过滤器)

文章目录

  • 微服务系列
  • 前言
  • 一、Spring Security + Oauth2 认证的4种模式
    • 1.1 授权码模式(authorization_code)
    • 1.2 简化模式(implicit)
    • 1.3 密码模式 (password)
    • 1.4 客户端模式(client_credentials)
  • 二、项目搭建
    • 2.1 添加依赖 pom.xml
    • 2.2 配置安全规则
      • 2.2.1 web安全规则配置
        • 2.2.2.1 规则配置类
        • 2.2.2.2 依赖的认证操作 provider
        • 2.2.2.3 加载用户信息的 UserDetailsService
      • 2.2.2 认证规则配置
        • 2.2.2.1 规则配置
        • 2.2.2.2 客户端信息查询服务
      • 2. Token 配置
    • 2.3 启动测试
      • 2.3.1 授权码测试
        • 2.3.1.1 请求获取授权码
        • 2.3.1.2 请求认证接口
      • 2.3.2 简化模式测试
        • 2.3.2.1 请求
        • 2.3.2.2 响应跳转
      • 2.3.3 密码模式测试
      • 2.3.4 客户端模式测试
  • 总结


前言

微服务开发这么流行,安全认证非常重要,Spring Security + Oauth2 作为一个 认证授权的框架,不可缺。
在此简单说明并且做学习笔记。
开发工具:IDEA


一、Spring Security + Oauth2 认证的4种模式

1.1 授权码模式(authorization_code)

授权码是相对比较安全的模式,需要用户首先通过接口获取一个 授权码,然后再用 client_id 和 client_secret 加上 获取到的授权码进行认证。

1.2 简化模式(implicit)

简化模式多用于单一应用,没有服务端的第三方单页面应用,因为没有服务器端就无法接受授权码。

1.3 密码模式 (password)

密码模式一般用于我们自己开发的,第一方原生APP或者是第一方面页面应用;因为会暴露密码出来,所以最好client端是自己开发的或者我们比较信任的

1.4 客户端模式(client_credentials)

我们对client足够信任的情况下使用客户端模式,客户端本身提供的 client_id 和 client_secret 进行认证

二、项目搭建

2.1 添加依赖 pom.xml

因为使用的是 Spring security + Oauth2 进行认证与授权的,所以要添加 Spring security 和 Oauth2 的依赖包;
也是一个web项目,所以需要添加 boot 的 web 包。
版本:版本是 2.x

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

2.2 配置安全规则

2.2.1 web安全规则配置

2.2.2.1 规则配置类

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    ClientAccessDeniedHandler clientAccessDeniedHandler;

    @Autowired
    UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;

    /**
     * 配置 Http 请求 安全策略
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()  //关闭 csrf 保护
          	.authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/login","/oauth/**").permitAll() //配置哪些路径可以访问
                .anyRequest().authenticated() //配置所有的请求都要权限验证
                .and()
                .formLogin().loginPage("/login.html") //自定义登录页面
                .and()
                .exceptionHandling().accessDeniedHandler(clientAccessDeniedHandler); //配置访问受限时的情况
        //super.configure(http);
    }

    /**
     * 配置一个 provider 进行用户信息的获取和验证操作
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(usernamePasswordAuthenticationProvider);
    }

    /**
     * 初始化一个 provider 提供校验操作
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(usernamePasswordAuthenticationProvider);
    }

    /**
     * 设置密码加密方式
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

2.2.2.2 依赖的认证操作 provider

@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {

    /**
     * 这里使用 UserDetailService 进行加载用户信息
     */
    @Autowired
    WeshUserDetailService userDetailService;

    /**
     * 认证方法
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String principle = authentication.getPrincipal().toString(); //username
        String credential = authentication.getCredentials().toString(); //password
        UserDetails userDetails = userDetailService.loadUserByUsername(principle);
        return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(),userDetails.getAuthorities());
    }

    /**
     * 判断是不是使用此 provider 进行认证操作,返回true 则在认证的时候执行上面的 authenticate() 方法
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }
}

2.2.2.3 加载用户信息的 UserDetailsService

@Component
public class WeshUserDetailService implements UserDetailsService {

    /**
     * 框架会掉此方法加载用户信息
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if("100001".equals(username)){
            User user = new User("100001","admin001", Collections.emptyList());
            return user;
        }
        return null;
    }
}

2.2.2 认证规则配置

2.2.2.1 规则配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter{

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private WeshClientDetailsService clientDetailsService;

    @Autowired
    private TokenStore tokenStore;

    /**
     * 配置登录用户信息服务
     * @param configurer
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
        //配置数据库获取客户信息
        configurer.withClientDetails(clientDetailsService);
        //super.configure(clients);
    }

    /**
     * 配置令牌
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) //需要提供一个认证管理器
                .accessTokenConverter(jwtAccessTokenConverter) //jwt 进行密码转换加密
                .tokenServices(tokenServices()) //token 管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);

        //super.configure(endpoints);
    }

    /**
     * 配置终端(api)
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()") //token/token_key
                .checkTokenAccess("permitAll()") //check_token
                .allowFormAuthenticationForClients(); //允许表单认证提交,不放开,登录页面表单提交不了
        //super.configure(security);
    }

    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService); //配置生成token获取用户信息服务
        tokenServices.setTokenStore(tokenStore); //token 存储服务
        tokenServices.setReuseRefreshToken(true);
        tokenServices.setAccessTokenValiditySeconds(7200);
        tokenServices.setReuseRefreshToken(false); // 设置为 true, 则没有 refresh_token 在返回体中
        tokenServices.setSupportRefreshToken(true); //支持 refresh token
        tokenServices.setRefreshTokenValiditySeconds(259200); //刷新令牌3天有效期
        //设置token的加密方式为 JWT
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        tokenServices.setTokenEnhancer(tokenEnhancerChain);
        return tokenServices;
    }
}

2.2.2.2 客户端信息查询服务

@Service
public class WeshClientDetailsService implements ClientDetailsService {

    /**
     * 实现具体如何读取用户信息
     * 项目中可以用从数据库中查询
     * @param s
     * @return
     * @throws ClientRegistrationException
     */
    @Override
    public ClientDetails loadClientByClientId(String s) throws ClientRegistrationException {
        Client client = new Client();
        if("100001".equals(clientId)){  //授权码模式需要提供跳转地址 100001 作为授权码账号
            client.setClientId("100001");
            client.setClientSecret(new BCryptPasswordEncoder().encode("admin001"));
            client.getScope().add("all"); //权限范围是all,所有
            client.getAuthorizedGrantTypes().add("authorization_code");
            client.getRegisteredRedirectUri().add("http://www.baidu.com");
        }
        if("100002".equals(clientId)){ // 100002 作为 password 模式跳转
            client.setClientId("100002");
            client.setClientSecret(new BCryptPasswordEncoder().encode("admin001"));
            client.getScope().add("all"); //权限范围是all,所有
        }
        return client;
    }
}

2. Token 配置

/**
 * Token 配置 jwt 加密
 */
@Configuration
public class TokenConfig {

    /**
     * jwt 配置的一个 签名
     */
    private final String SIGNING_KEY = "*****";

    /**
     * 初始化 token store 来存储token
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * jwt 加密token配置
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey(SIGNING_KEY);
        return tokenConverter;
    }
}

2.3 启动测试

2.3.1 授权码测试

2.3.1.1 请求获取授权码

http://localhost:9999/uaa/oauth/authorize?client_id=client01&response_type=code&scope=all&redirect_uri=http://www.baidu.com (GET)

参数说明:
client_id: 用户id
client_secret: 用户密码
response_type: 请求认证方式,值为 ‘code’
Spring Security + Oauth2 认证授权_第1张图片
点击Authorize之后,会跳转到 redirect url,并传参授权码
code 就是授权码

2.3.1.2 请求认证接口

按照授权码获取token
Spring Security + Oauth2 认证授权_第2张图片

2.3.2 简化模式测试

2.3.2.1 请求

http://localhost:9999/uaa/oauth/authorize?client_id=client01&response_type=token&scope=all&redirect_uri=http://www.baidu.com (GET)
注意:response_type是 token
调用授权接口,和 授权码模式同一个接口,不过 response_type 是 token
Spring Security + Oauth2 认证授权_第3张图片

2.3.2.2 响应跳转

返回结果:
注意:链接中的 ‘#’ , URI 中的 fragment 用来标识初级资源
跳转回来接口带了 token

2.3.3 密码模式测试

http://localhost:9999/uaa/oauth/token(POST)
参数
client_id=client01,
client_secret=client01,
grant_type=password,
username=zhangsan,
password=123
Spring Security + Oauth2 认证授权_第4张图片

2.3.4 客户端模式测试

http://localhost:9999/uaa/oauth/token
参数
client_id=client01
client_secret=client01
grant_type=client_credentials
Spring Security + Oauth2 认证授权_第5张图片

总结

spring security + oauth2进行了简单的搭建和记录,大致理解了 spring security oauth2 框架,项目初始搭建完成,不过此框架还有很多细节部分需要再深究。
自己总结的往往影响比较深刻,哪怕忘记了,看自己的文章也更熟悉。
而且我的文章目录清晰,可以迅速定位到需要看的那部分。
项目源码
有详细注释。
如果令尊看到这里了,希望不要浪费您的宝贵的实际,这篇简短的文章能帮助到令尊!!!

你可能感兴趣的:(Spring,JAVA后台,spring,java,后端,web安全,安全架构)