oauth授权登陆

目录

背景

Oauth2.0简介

时序图设计

具体实现

一、oauthServer服务

二、loginServer服务

三、resourceServer服务

四、cleint服务

演示


背景

公司做平台产品,很早之前就想着要做类似于微信,QQ那种的授权登陆系统,用来统一授权子系统。于是在网上搜罗很多大神的讲解后,根据自己想法写出一套完整的oauth授权登陆系统

Oauth2.0简介

oauth2.0是OAuth协议的延续版本,是一个互联网标准的用户验证和授权协议,通俗讲就是前人想出的一套用于用户验证和授权的方案,其大致流程如下:

       (A)用户打开客户端以后,客户端要求用户给予授权。

  (B)用户同意给予客户端授权。

  (C)客户端使用上一步获得的授权,向认证服务器申请令牌。

  (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

  (E)客户端使用令牌,向资源服务器申请获取资源。

  (F)资源服务器确认令牌无误,同意向客户端开放资源。

同时在客户端规定了四种授权模式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials)。本次介绍内容使用了授权码模式,也是功能最完整、流程最严密的授权模式。

时序图设计

此流程与网上众多关于oauth登陆的设计大同小异,唯一需要特殊说明的就是多设立了一个loginServer服务,此处目的是将授权系统中的用户信息校验解耦,使此系统可灵活接入其他平台的现有账户体系进行使用。

具体实现

一、oauthServer服务

1、建立一个空的maven项目,具体方式不再赘述,根据自己IDE或喜好创建就好

2、引入相关jar包,pom.xml内容如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.colin</groupId>
    <artifactId>oauthServer</artifactId>
    <version>1.0</version>

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


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-security</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <!-- jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

    </dependencies>
</project>

3、配置springboot配置文件application.properties,具体如下:

# 配置当前服务端口
server.port=1111
# 配置thymeleaf
spring.thymeleaf.prefix=classpath:/views/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
# redis配置
spring.redis.host=*
spring.redis.port=*
spring.redis.password=*
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=3000
spring.redis.pool.max-idle=20
spring.redis.pool.min-idle=5
# jdbc配置
spring.datasource.url=*
spring.datasource.username=*
spring.datasource.password=*
# mybatis配置
mybatis.type-aliases-package=com.colin
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.example=debug
# 开启此配置使我们可以自定义扩展springBoot默认的bean
spring.main.allow-bean-definition-overriding=true

4、配置启动类

package com.colin.oauth;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@SpringBootApplication
@EnableAuthorizationServer//oauth认证服务
@MapperScan("com.colin.mapper")//指定mybatis的mapper路径,此次几乎无用
public class OauthServer {
    public static void main(String[] args) {
        SpringApplication.run(OauthServer.class, args);
    }
}

5、配置security

package com.colin.oauth.config;

import com.colin.oauth.service.UserService;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Resource
    private UserService userService;

    /**
     * 指定一种加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置基本用户信息及加密方式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    /**
     * 配置了一个表单登陆
     * 访问此服务时会需要先进行登陆
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
             //关闭打开的csrf保护
             csrf().disable().
             //配置一个表单登陆
             formLogin().
             //自定义登陆页面地址
             loginPage("/auth/login")
        ;
    }

}

此类中的UserService是自定义的用户信息获取接口服务,如下:

package com.colin.oauth.service;

import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * 继承security中的service接口
 * 用来自定义当前系统用户信息获取
 */
public interface UserService extends UserDetailsService{
}

6、配置authorization

package com.colin.oauth.config;

import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

@Configuration
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter{
    @Resource
    private DataSource dataSource;
    @Resource
    private PasswordEncoder passwordEncoder;

    /**
     * 生成的 Token 要往哪里存储,
     * 我们可以存在 Redis中,也可以存在内存中,
     * 也可以结合 JWT 等等,
     * 最终使用jdbc存储,数据可读性更强些
     * 存储表:oauth_access_token,oauth_refresh_token
     */
    @Bean
    public TokenStore tokenStore(){
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 配置授权码的存储
     * 由于一次性,暂放在当前内存中
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new InMemoryAuthorizationCodeServices();
    }

    /**
     *  授权信息保存策略
     *  当前保存至JDBC
     *  存储表:oauth_approvals
     */
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 覆盖默认ClientDetailsService的实现
     * 将客户端校验信息存入数据库
     * 存储表:oauth_client_details
     */
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService(){
        JdbcClientDetailsService detailsService = new JdbcClientDetailsService(dataSource);
        detailsService.setPasswordEncoder(passwordEncoder);
        return detailsService;
    }

    /**
     * 配置令牌的存储与认证
     */
    @Bean
    public AuthorizationServerTokenServices authorizationServerTokenServices(){
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(jdbcClientDetailsService());
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore());
        //设置access_token失效时间
        services.setAccessTokenValiditySeconds(60*60*2);
        //设置refresh_token失效时间
        services.setRefreshTokenValiditySeconds(60*60*3);
        return services;
    }

    /**
     * 用来配置令牌端点的安全约束,也就是这个端点谁能访问,谁不能访问
     * checkTokenAccess 是指一个 Token 校验的端点,
     * 这个端点我们设置为可以直接访问
     * 在后面,当资源服务器收到 Token 之后,需要去校验Token的合法性,就会访问这个端点
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    /**
     * 用来配置客户端的详细信息
     * 配置校验客户端
     * 这里我们分别配置了客户端的 id,secret、资源 id、授权类型、授权范围以及重定向 uri
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //改为jdbc模式
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     * 配置令牌的访问端点和令牌服务
     * OAuth2的主配置信息
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .approvalStore(approvalStore())
                .authorizationCodeServices(authorizationCodeServices())
                .tokenServices(authorizationServerTokenServices());
        //自定义授权页面配置
        endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");
    }

}

7、编写自定义UserService的实现UserServiceImpl

package com.colin.oauth.service.impl;

import com.colin.oauth.entity.Grant;
import com.colin.oauth.entity.SysUser;
import com.colin.oauth.enums.GrantEnum;
import com.colin.oauth.service.UserService;
import com.colin.oauth.util.SysSecurityUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{

    @Resource
    private PasswordEncoder passwordEncoder;

    /**
     * 为方便校验,此处写死待校验用户
     */
    private static HashSet<String> USERS = new HashSet<>();
    static {
        USERS.add("colin1");
        USERS.add("colin2");
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        //可从其他服务(如loginServer底层服务)获取用户信息进行校验
        //也可以从本地数据库读取进行验证
        if(!USERS.contains(s)){
            throw new UsernameNotFoundException("用户不存在");
        }
        SysUser user = new SysUser();
        user.setUsername(s);

        //此处SysSecurityUtil定义了一套前后一致的加密规则,省去密码再次读取
        //也可直接从其他服务或本地数据库获取密码信息,再使用passwordEncoder进行编码
        user.setPassword(passwordEncoder.encode(SysSecurityUtil.generatePwd(s)));
        Grant grant = new Grant();
        grant.setAuthorities(GrantEnum.USER.name());

        List<Grant> list = new ArrayList<>();
        list.add(grant);
        user.setGrants(list);

        return user;
    }
}

8、涉及的实体及枚举如下

package com.colin.oauth.entity;

import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * 实现security封装的UserDetails进行扩展,
 * 便于自定义用户信息操作
 */
public class SysUser implements UserDetails{

    /**
     * 用户名
     */
    private String username;
    /**
     * 用户密码
     */
    private String password;
    /**
     * 用户权限
     */
    private List<Grant> grants;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setGrants(List<Grant> grants) {
        this.grants = grants;
    }

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

    @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;
    }
}
package com.colin.oauth.entity;

import org.springframework.security.core.GrantedAuthority;

/**
 * 实现security封装的GrantedAuthority权限对象进行扩展,
 * 便于自定义用户权限操作
 */
public class Grant implements GrantedAuthority {

    //用户权限
    private String authorities;

    @Override
    public String getAuthority() {
        return authorities;
    }

    public void setAuthorities(String authorities) {
        this.authorities = authorities;
    }
}
package com.colin.oauth.enums;

public enum GrantEnum {
    /**
     * 管理员权限
     */
    ADMIN,
    /**
     * 普通用户权限
     */
    USER
}

9、自定义页面路由

此处写了2个controller,一个用来自定义登陆路由处理,一个自定义授权路由处理

package com.colin.oauth.controller;

import com.colin.oauth.util.SysSecurityUtil;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

    /**
     * 路由到第三方页面
     */
    @GetMapping("/auth/login")
    public void loginPage(Model model, HttpServletResponse response){
        try {
            response.sendRedirect("http://192.168.1.227:1114/login/page");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 第三方登陆成功回调此接口
     */
    @GetMapping("/oauthServer/login")
    public String oauthLogin(String u,Model model){
        model.addAttribute("username", u);
        //此处为了方便,直接加密生成密码,与UserService中的验证相呼应
        //后续可以考虑不再设置
        model.addAttribute("pwd", SysSecurityUtil.generatePwd(u));
        return "login";
    }

}
package com.colin.oauth.controller;

import java.util.Map;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

/**
 * 由oauth封装时将认证信息放置在session中,此处需要@SessionAttributes
 * 以便获取认证信息
 */
@Controller
@SessionAttributes("authorizationRequest")
public class GrantController {

    @RequestMapping("/custom/confirm_access")
    public String grantPage(Map<String, Object> model, Model viewModel){
        AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
        viewModel.addAttribute("clientId", authorizationRequest.getClientId());
        return "grant";
    }
}

10、自定义登陆授权页面

根据application.properties中的配置,将html文件放置在目录/resources/views/下

本次用到的两个html页面大致代码如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>

<style>
    .login-container {
        margin: 50px;
        width: 100%;
    }

    .form-container {
        margin: 0px auto;
        width: 50%;
        text-align: center;
        box-shadow: 1px 1px 10px #888888;
        height: 300px;
        padding: 5px;
    }

    input {
        margin-top: 10px;
        width: 350px;
        height: 30px;
        border-radius: 3px;
        border: 1px #E9686B solid;
        padding-left: 2px;

    }
    .btn {
        width: 350px;
        height: 35px;
        line-height: 35px;
        cursor: pointer;
        margin-top: 20px;
        border-radius: 3px;
        background-color: #E9686B;
        color: white;
        border: none;
        font-size: 15px;
    }

    .title{
        margin-top: 5px;
        font-size: 18px;
        color: #E9686B;
    }
</style>
<body>
<div class="login-container" style="display: none">
    <div class="form-container">
        <p class="title">用户登录</p>
        <form id="loginForm" method="post" action="/auth/login">
            <input type="text" name="username" th:value="${username}" placeholder="用户名"/>
            <br>
            <input type="password" name="password" th:value="${pwd}" placeholder="密码"/>
            <br>
            <button type="submit" class="btn">登 &nbsp;&nbsp; 录</button>
        </form>
        <p style="color: red" th:if="${param.error}">用户名或密码错误</p>
    </div>
</div>
<script>
    var loginForm = document.getElementById("loginForm");
    loginForm.submit();
</script>
</body>
</html>

注意:此登陆页面只作为一个与spring-security衔接的跳转使用,实际的登陆逻辑实现由loginServer服务提供

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>授权</title>
</head>
<style>

    body{
        margin:0;
        padding:0;
        background-color: white;
    }
    html{
        padding: 0;
        margin: 0;
    }
    .title {
        background-color: #e90908;
        height: 50px;
        padding-left: 20%;
        padding-right: 20%;
        color: white;
        line-height: 50px;
        font-size: 18px;
        text-align: center;
    }
    .container{
        clear: both;
        text-align: center;
    }
    .btn {
        width: 350px;
        height: 35px;
        line-height: 35px;
        cursor: pointer;
        margin-top: 20px;
        border-radius: 3px;
        background-color: #e90908;
        color: white;
        border: none;
        font-size: 15px;
    }
</style>
<body style="margin: 0">
<div class="title">自定义授权页面</div>
<div class="container">
    <h3>该应用将获取你的以下信息</h3>
    <p>昵称,头像和性别</p>
    <form method="post" action="/oauth/authorize">
        <input type="hidden" name="user_oauth_approval" value="true">
        <input type="hidden" name="scope.all" value="true"/>
        <button class="btn" type="submit"> 同意</button>
    </form>
</div>
</body>
</html>

二、loginServer服务

1、建立一个空的maven项目

2、引入jar,此处只是一个简单的demo项目,只为完成流程的通顺,所以之引入了springboot的web基础包,以及页面使用的thymeleaf包。实际项目中需要根据自己的需求进行开发,对应pom.xml如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.colin</groupId>
    <artifactId>loginServer</artifactId>
    <version>1.0</version>

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

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

</project>

3、配置springboot配置文件application.properties

# 配置当前服务端口
server.port=1112
# 配置thymeleaf
spring.thymeleaf.prefix=classpath:/views/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false

4、配置启动类

package com.colin.login;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LoginServer {
    public static void main(String[] args) {
        SpringApplication.run(LoginServer.class, args);
    }
}

5、登陆接口

package com.colin.login.controller;

import java.util.HashSet;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class LoginController {

    /**
     * 与oauthServer中用户校验一致,此处写死待校验用户
     * 实际项目中需要根据自身业务处理
     */
    private static HashSet<String> USERS = new HashSet<>();
    static {
        USERS.add("colin1");
        USERS.add("colin2");
    }

    /**
     * 跳转登陆页面
     */
    @GetMapping("/login/page")
    public String loginPage(Model model){
        return "login";
    }

    /**
     * 模拟登陆
     */
    @PostMapping("/login")
    public String toLogin(Model model,String username, String password, HttpServletResponse response){
        try {
            //模拟登陆逻辑
            if(!USERS.contains(username)){
                model.addAttribute("error", "用户不存在");
                return "login";
            }
            if(!"1".equals(password)){
                model.addAttribute("error", "用户名密码错误");
                return "login";
            }
            //登陆成功,返回oauthServer并携带登陆成功后的用户信息
            response.sendRedirect("http://192.168.1.227:1111/oauthServer/login?u="+username);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "login";
    }

}

6、登陆页面

根据配置,h5页面路径为:/resources/views/

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>

<style>
    body{
        margin:0;
        padding:0;
        background-color: white;
    }

    .login-container {
        top: 10px;
        margin: 0;
        width: 100%;
        background-color: white;
    }

    .form-container {
        margin: 0 auto;
        width: 50%;
        text-align: center;
        box-shadow: 1px 1px 10px #888888;
        height: 300px;
        padding: 5px;
        background-color: azure;
    }

    input {
        margin-top: 10px;
        width: 350px;
        height: 30px;
        border-radius: 3px;
        border: 1px #e90908 solid;
        padding-left: 2px;

    }
    .btn {
        width: 350px;
        height: 35px;
        line-height: 35px;
        cursor: pointer;
        margin-top: 20px;
        border-radius: 3px;
        background-color: #e90908;
        color: white;
        border: none;
        font-size: 15px;
    }

    .title{
        margin-top: 5px;
        font-size: 18px;
        color: #e90908;
    }
</style>
<body>
<div class="login-container">
    <div class="form-container">
        <p class="title">模拟第三方登陆页面</p>
        <form name="loginForm" method="post" action="/login">
            <input type="text" name="username" placeholder="用户名"/>
            <br>
            <input type="password" name="password" placeholder="密码"/>
            <br>
            <button type="submit" class="btn">登 &nbsp;&nbsp; 录</button>
        </form>
        <p style="color: red" th:if="${error}" th:text="${error}"></p>
    </div>
</div>
</body>
</html>

三、resourceServer服务

1、创建空的maven项目

2、引入jar包,同样,此处只为整体流程跑通,设置了简单的接口,正式项目需根据自己需求进行扩展

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.colin</groupId>
    <artifactId>resourceServer</artifactId>
    <version>1.0</version>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-security</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>

3、配置springboot配置文件application.properties

server.port=1113

4、配置启动类

package com.colin.resource;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer//资源服务
public class ResourceServer {
    public static void main(String[] args) {
        SpringApplication.run(ResourceServer.class, args);
    }
}

5、配置服务访问安全策略

package com.colin.resource.config;

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.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;

@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{

    /**
     * 配置了 access_token 的校验地址、
     * client_id、
     * client_secret
     * 这三个信息,
     * 当用户来资源服务器请求资源时,会携带上一个 access_token,
     * 通过这里的配置,就能够校验出 token 是否正确等
     */
    @Bean
    public RemoteTokenServices tokenServices(){
        RemoteTokenServices services = new RemoteTokenServices();
        services.setCheckTokenEndpointUrl("http://192.168.1.227:1111/oauth/check_token");
        //经测试,此处传递的client信息只要在oauth_client_details中存在即可
        //后续可设计为本系统主账号信息做统一校验
        services.setClientId("javaboy");
        services.setClientSecret("1");
        return services;
    }

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //可直接访问的地址
                .antMatchers("/access/**").permitAll()
                //需要admin权限访问的地址
                .antMatchers("/admin/**").hasRole("admin")
                //其他访问都需要验证
                .anyRequest().authenticated()
                .and()
                //使支持跨域
                .cors()
        ;
    }
}

6、模拟资源接口

package com.colin.resource.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HellowController {

    /**
     * 受限制访问,用户权限不限
     */
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

    /**
     * 受限制访问,只允许admin权限用户访问
     */
    @GetMapping("/admin/hello")
    public String adminHello(){
        return "adminHello";
    }

    /**
     * 不受限制
     */
    @GetMapping("/access/hello")
    public String accessHello(){
        return "accessHello";
    }

}

四、cleint服务

此服务就是一个简单的页面去访问授权连接即可,为了方便跳转,另起一个端口作为页面服务来模拟第三方

1、创建一个空的maven项目

2、引入jar

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.colin</groupId>
    <artifactId>client</artifactId>
    <version>1.0</version>

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

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

3、配置文件

server.port=1114

4、启动类

package com.colin.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ClientServer {
    public static void main(String[] args) {
        SpringApplication.run(ClientServer.class, args);
    }
}

5、模拟第三方页面,放置在/resource/static/下即可,直接访问测试

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>模拟第三方页面</title>
</head>
<body>
    <div>
        <a href="http://192.168.1.227:1111/oauth/authorize?client_id=colin0702&response_type=code&scope=all&redirect_uri=http://192.168.1.227:1114/index.html">
            点击授权
        </a>
    </div>
</body>
</html>
 

client_id:第三方申请应用时生成,存放在oauth_client_details表中

response_type:授权模式,code即表示为授权码模式

scope:默认all即可

redirect_uri:授权之后回调页面地址,此地址需要也第三方申请应用时填写的地址一致,也存放在oauth_client_details表中

至此,简单的一套oauth2.0授权系统基本完成!

演示

1、分别启动上边写好的服务

2、插入一条第三方申请信息,对于相关表的创建sql会在最后整理出来

/**测试数据*/
INSERT INTO `oauth`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('colin0702', 'res1', '$2a$10$dUD2wWPtFpHfI3TpAfdFBuL0TOpztgvz2xmOMip4ZNn2EWrDh9xYq', 'all', 'authorization_code,refresh_token', 'http://192.168.1.227:1114/index.html', NULL, NULL, NULL, NULL, NULL);

3、访问client服务的静态页面,模拟第三方页面

4、点击授权,访问oauthServer,之后会重新向至loginServer的登陆页面,如下:

5、输入预设的用户名密码,在loginServer服务登陆成功后,重定向至oauthServer服务跳转至授权页面

6、点击同意,oauthServer服务根据设定的回调地址,返回H5页面,并携带code信息

7、利用postman,模拟后台请求oauthServer服务,获取access_token信息

8、使用postman,模拟第三方访问资源服务resourceServer

对于admin权限的访问尝试

以上就是整套oauth2.0系统的实现尝试,利用了spring-cloud-security及spring-cloud-starter-oauth2中的成熟API及扩展,有想要查看全部代码及相关建表语句的可前往https://github.com/colinguangyi/oauth

另:未避免github访问延迟,将项目转至https://gitee.com/zhaolz/oauth

对于文中有不妥之处的,还望各位大神指点~

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值