【Spring-Security】Re10 Oauth2协议 P1 授权码模式 & 密码模式

一、Oauth2协议:

第三方登录,即忘记本站密码,但是登录界面中提供了一些第三方登录,例如微信、支付宝、QQ、等等,通过第三方授权实现登录

第三方认证技术主要解决的时认证标准,各个平台的登录要遵循统一的接口协议

所以这里采用的方案是Oauth2

资料参考:

https://www.jianshu.com/p/84a4b4a1e833

二、Spring-Security + Oauth2 环境搭建

创建一个空的SpringBoot项目:

删除不必要的文件

导入POM依赖坐标:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.zeal4j</groupId>
    <artifactId>so2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>so2</name>
    <description>Spring-Security + Oauth2 project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <!--<version>2.2.4.RELEASE</version>-->
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
            <!--<version>2.2.4.RELEASE</version>-->
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version><!--<version>Greenwich.SR2</version>-->
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

用户实体类:

package cn.zeal4j.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:37
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
    private String username;
    private String password;
    private Collection<GrantedAuthority> authorities;


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

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

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

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

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

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

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

Security安全配置:

package cn.zeal4j.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:35
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // csrf
        httpSecurity.csrf().disable();

        // 登录,退出,授权放行
        httpSecurity.authorizeRequests().antMatchers("/login/**","/logout/**","/oauth/**/").permitAll();

        // 允许表单登录
        httpSecurity.formLogin().permitAll();

        // 其余请求权限拦截
        httpSecurity.authorizeRequests().anyRequest().authenticated();
    }
}

授权服务配置:

package cn.zeal4j.configuration;

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

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:48
 * @description 授权服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.
                inMemory().
                withClient("admin").
                secret(passwordEncoder.encode("112233")).
                accessTokenValiditySeconds(3600). // 令牌有效时间 一小时
                redirectUris("http://www.baidu.com"). // 授权成功的跳转
                scopes("all").  // 所有范围
                authorizedGrantTypes("authorization_code");     // 授权类型:授权码模式
    }
}

资源服务配置:

package cn.zeal4j.configuration;
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;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:57
 */

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity.authorizeRequests().
                anyRequest().authenticated(). // 默认任何请求都需要授权
                and().
                requestMatchers().antMatchers("/user/**"); // 资源匹配规则
    }
}

登录逻辑:

package cn.zeal4j.service.impl;

import cn.zeal4j.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:35
 */
@Service public class CustomUserDetailsServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { String encode = passwordEncoder.encode("123456"); return new User("admin", encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }

User资源控制器:

package cn.zeal4j.controller;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 12:01
 */
@Controller
@RequestMapping("user")
public class UserController {

    /**
     * 获取当前用户
     * @param authentication
     * @return user/getCurrentUser
     */
    @RequestMapping("/getCurrentUser")
    @ResponseBody
    public Object getCurrentUser(Authentication authentication) {
        return authentication.getPrincipal();
    }

}

启动访问发现:

二、授权码模式实现:

发现生成的UserDetails实例默认设置的都是False,SpringSecurity一经权限判断,直接触发拦截

所以默认这里都要改成TRUE放行通过

    @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;
    }

再次测试Oauth2地址:

http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all

点击选择Approve允许,点击Authorize进行授权

https://www.baidu.com/?code=G6ocWn

这个code就是我们的授权码

现在使用Postman进行测试:

 1、先选择认证账号信息输入:

2、然后输入请求参数:

grant_type    authorization_code
code             你得到的授权码
cient_id        admin 
redirect_uri   http://www.baidu.com
scope            all

请求成功则会返回这个消息JSON

{
    "access_token": "4c562cc7-07c4-47b6-9a5b-537c4f4079cd",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "all"
}

得到了许可Token

接着用Token访问资源获取接口:

http://localhost:8080/user/getCurrentUser

得到我们返回的数据:

{
    "username": "admin",
    "password": "$2a$10$tBlHK4KbNFsE7F7usl6AsODWWBkF4oS8Ak4qkaNbEFAsKykvXgH9i",
    "authorities": [
        {
            "authority": "admin"
        }
    ],
    "enabled": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "accountNonExpired": true
}

如果令牌是错误的

{
    "error": "invalid_token",
    "error_description": "Invalid access token: 2287c3ab-3dc5-475e-97eb-bdffb2a78b8A"
}

三、密码模式授权实现:

package cn.zeal4j.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
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;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:48
 * @description 授权服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    @Qualifier("customUserDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 使用密码模式需要的配置方法
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.
                authenticationManager(authenticationManager).
                userDetailsService(userDetailsService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.
                inMemory().
                withClient("admin").
                secret(passwordEncoder.encode("112233")).
                // accessTokenValiditySeconds(3600). // 令牌有效时间 一小时
                redirectUris("http://www.baidu.com"). // 授权成功的跳转
                scopes("all").  // 所有范围
                // authorizedGrantTypes("authorization_code");     // 授权类型:授权码模式
                authorizedGrantTypes("password");     // 授权类型:密码模式
    }
}

授权管理器Bean的注入:

package cn.zeal4j.configuration;

import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author Administrator
 * @file Spring-Security + Oauth2
 * @create 2020 09 29 11:35
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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

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

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


//        // csrf
//        httpSecurity.csrf().disable();
//
//        // 登录,退出,授权放行
//        httpSecurity.authorizeRequests().antMatchers("/login/**","/logout/**","/oauth/**/").permitAll();
//
//        // 允许表单登录
//        httpSecurity.formLogin().permitAll();
//
//        // 其余请求权限拦截
//        httpSecurity.authorizeRequests().anyRequest().authenticated();
    }
}

Postman请求的地址和前面的密码不变,但是更改授权参数

{
    "access_token": "8e51bdc3-50cd-42bd-b41c-28f06e5eca1f",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "all"
}

用密码模式的令牌获取用户主体,也能拿到用户主体的信息:

原文地址:https://www.cnblogs.com/mindzone/p/13748996.html