欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Springboot Oauth2 Server搭建Oauth2认证服务

程序员文章站 2023-08-16 15:32:36
本教程源码 https://github.com/bestaone/hiauth 源码比较全面,教程我就只介绍关键代码了,喜欢的点个star,谢谢! 关键词...

本教程源码
https://github.com/bestaone/hiauth

源码比较全面,教程我就只介绍关键代码了,喜欢的点个star,谢谢!

关键词

  • 微服务认证
  • oauth2
  • 认证中心
  • springboot
  • spring-cloud-starter-oauth2
  • 集成oauth2
  • oauth2 客户端

介绍

这里我将介绍两个部分

  • oauth2 server 的开发 (hi-auth-web模块)
  • oauth2 client 的开发 (hi-mall-web模块)

效果图

详解Springboot Oauth2 Server搭建Oauth2认证服务

himall.gif

详解Springboot Oauth2 Server搭建Oauth2认证服务

umc.gif

live demo

himall:

umc:

swagger2:

oauth2 server 搭建

数据库表(mysql5.6),其中只有sys_user表由我们自己控制,其他表由框架控制

create table `clientdetails` (
 `appid` varchar(255) not null,
 `resourceids` varchar(256) default null,
 `appsecret` varchar(256) default null,
 `scope` varchar(256) default null,
 `granttypes` varchar(256) default null,
 `redirecturl` varchar(256) default null,
 `authorities` varchar(256) default null,
 `access_token_validity` int(11) default null,
 `refresh_token_validity` int(11) default null,
 `additionalinformation` varchar(4096) default null,
 `autoapprovescopes` varchar(256) default null,
 primary key (`appid`)
) engine=innodb default charset=utf8;

create table `oauth_access_token` (
 `token_id` varchar(256) default null,
 `token` blob,
 `authentication_id` varchar(255) not null,
 `user_name` varchar(256) default null,
 `client_id` varchar(256) default null,
 `authentication` blob,
 `refresh_token` varchar(256) default null,
 primary key (`authentication_id`)
) engine=innodb default charset=utf8;

create table `oauth_approvals` (
 `userid` varchar(256) default null,
 `clientid` varchar(256) default null,
 `scope` varchar(256) default null,
 `status` varchar(10) default null,
 `expiresat` timestamp not null default current_timestamp on update current_timestamp,
 `lastmodifiedat` timestamp not null default '0000-00-00 00:00:00'
) engine=innodb default charset=utf8;

create table `oauth_client_details` (
 `client_id` varchar(255) not null,
 `resource_ids` varchar(256) default null,
 `client_secret` varchar(256) default null,
 `scope` varchar(256) default null,
 `authorized_grant_types` varchar(256) default null,
 `web_server_redirect_uri` varchar(2560) default null,
 `authorities` varchar(256) default null,
 `access_token_validity` int(11) default null,
 `refresh_token_validity` int(11) default null,
 `additional_information` varchar(4096) default null,
 `autoapprove` varchar(256) default null,
 primary key (`client_id`)
) engine=innodb default charset=utf8;

insert into `oauth_client_details` values ('client', null, '$2a$10$1n/.lvtjuypvxdzoj1kdvupddv/kdsqe9cxm9bzb1preyzk6gmfre', 'all,auth,user,goods,order', 'authorization_code,client_credentials,password,refresh_token', 'http://localhost:8081/mall/callback,http://localhost:9080/user/webjars/springfox-swagger-ui/oauth2-redirect.html,http://localhost:9081/goods/webjars/springfox-swagger-ui/oauth2-redirect.html,http://localhost:9082/order/webjars/springfox-swagger-ui/oauth2-redirect.html,http://localhost/user/webjars/springfox-swagger-ui/oauth2-redirect.html,http://localhost/goods/webjars/springfox-swagger-ui/oauth2-redirect.html,http://localhost/order/webjars/springfox-swagger-ui/oauth2-redirect.html', 'role_user', '1800', '86400', null, 'false');

create table `oauth_client_token` (
 `token_id` varchar(256) default null,
 `token` blob,
 `authentication_id` varchar(255) not null,
 `user_name` varchar(256) default null,
 `client_id` varchar(256) default null,
 primary key (`authentication_id`)
) engine=innodb default charset=utf8;

create table `oauth_code` (
 `code` varchar(256) default null,
 `authentication` blob
) engine=innodb default charset=utf8;

create table `oauth_refresh_token` (
 `token_id` varchar(256) default null,
 `token` blob,
 `authentication` blob
) engine=innodb default charset=utf8;

create table `sys_user` (
 `id` bigint(20) not null,
 `name` varchar(20) default null,
 `username` varchar(20) not null,
 `password` varchar(128) not null,
 `tel` varchar(20) default null,
 `gender` varchar(10) default null,
 `createtime` datetime default null,
 primary key (`id`),
 unique key `unique_username` (`username`),
 unique key `unique_tel` (`tel`)
) engine=innodb default charset=utf8;

insert into `sys_user` values ('1', '张三', 'admin', '123456', '13712345678', 'male', '2018-12-03 17:57:12');
insert into `sys_user` values ('2', '李四', 'user', '123456', '13812345678', 'unknown', '2018-12-03 17:57:12');

pom.xml如下

<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-security</artifactid>
</dependency>
<dependency>
  <groupid>org.springframework.cloud</groupid>
  <artifactid>spring-cloud-starter-oauth2</artifactid>
  <version>2.0.1.release</version>
</dependency>
<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-thymeleaf</artifactid>
</dependency>
<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.0.0</version>
</dependency>

添加表sys_user的service、mapper

@mapper
public interface usermapper {

  @insert("insert into sys_user(id,name,username,password,tel,gender,createtime) values(#{id},#{name},#{username},#{password},#{tel},#{gender},#{createtime})")
  void insert(user user);

  @delete("delete from sys_user where id = #{id}")
  void delete(long id);

  @update("update sys_user set name=#{name},username=#{username},password=#{password},tel=#{tel},gender=#{gender},createtime=#{createtime} where id =#{id}")
  int update(user user);

  @resultmap("baseresultmap")
  @select("select * from sys_user where id=#{id}")
  user findbyid(long id);

  @resultmap("baseresultmap")
  @select("select * from sys_user where username=#{username}")
  user findbyusername(string username);

  @resultmap("baseresultmap")
  @select("select * from sys_user where tel=#{tel}")
  user findbytel(string tel);

  @resultmap("baseresultmap")
  @select("select * from sys_user")
  list<user> findall();

  @resultmap("baseresultmap")
  @select("select * from sys_user where name like #{name}")
  list<user> findbyname(string name);

}

@service
public class userserviceimpl implements userservice {

  @resource
  usermapper mapper;

  @override
  public user save(user user) {
    if(user.getid()!=null){
      mapper.update(user);
    } else {
      user.setid(system.currenttimemillis());
      mapper.insert(user);
    }
    return user;
  }

  @override
  public user findbyid(long id) {
    return mapper.findbyid(id);
  }

  @override
  public user findbyusername(string username) {
    return mapper.findbyusername(username);
  }

  @override
  public user findbytel(string tel) {
    return mapper.findbytel(tel);
  }

  @override
  public list<user> findall() {
    return mapper.findall();
  }

  @override
  public void delete(long id) {
    mapper.delete(id);
  }

  @override
  public list<user> findbyname(string name) {
    return mapper.findbyname("%" + name + "%");
  }

}

添加登录拦截

@configuration
@enablewebsecurity
@enableglobalmethodsecurity(prepostenabled = true)
public class securityconfig extends websecurityconfigureradapter {

  @bean
  public passwordencoder passwordencoder(){
    return new bcryptpasswordencoder();
  }

  @bean
  public userdetailsservice simpleuserdetailsservice(){
    return new userdetailsserviceimpl();
  }

  @override
  protected void configure(authenticationmanagerbuilder auth) throws exception {
    auth.userdetailsservice(simpleuserdetailsservice());
  }

  @override
  @bean
  public authenticationmanager authenticationmanagerbean() throws exception {
    return super.authenticationmanagerbean();
  }

  @override
  protected void configure(httpsecurity http) throws exception {

    http.userdetailsservice(userdetailsservice());
    http.csrf().disable();
    http.formlogin()
        .loginpage("/signin").loginprocessingurl("/signin/form/account").defaultsuccessurl("/index")
        .and()
        .logout().logouturl("/signout").logoutsuccessurl("/signin")
        .and()
        .authorizerequests()
        .antmatchers("/signin","/signin/form/tel","/code/image","/code/mobile","/static/**").permitall()
        .antmatchers("/oauth/**").permitall()
        .antmatchers("/user/**").hasanyrole("user","admin")
        .anyrequest().authenticated();

  }

}

添加登录表单signin.html

<div class="tab-pane fade in active" id="account-login">
  <form th:action="@{/signin/form/account}" method="post">
    <label for="username" class="sr-only">用户名</label>
    <input class="form-control" type="text" name="username" id="username" value="user" placeholder="账号" required>
    <label for="password" class="sr-only">密码</label>
    <input class="form-control" type="password" name="password" id="password" value="123456" placeholder="密码" required>
    <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
  </form>
</div>

oauth2 server config

@configuration
@enableauthorizationserver
public class oauth2authorizationconfig extends authorizationserverconfigureradapter {

  @autowired
  private environment env;

  @autowired
  private authenticationmanager authenticationmanager;

  /**
   * 自定义授权页面
   */
  @autowired
  private authorizationendpoint authorizationendpoint;

  @postconstruct
  public void init() {
    authorizationendpoint.setuserapprovalpage("forward:/oauth/my_approval_page");
    authorizationendpoint.seterrorpage("forward:/oauth/my_error_page");
  }

  @bean
  public datasource datasource() {
    final drivermanagerdatasource datasource = new drivermanagerdatasource();
    datasource.setdriverclassname(env.getproperty("spring.datasource.driver-class-name"));
    datasource.seturl(env.getproperty("spring.datasource.url"));
    datasource.setusername(env.getproperty("spring.datasource.username"));
    datasource.setpassword(env.getproperty("spring.datasource.password"));
    return datasource;
  }

  @bean
  public approvalstore approvalstore() {
    return new jdbcapprovalstore(datasource());
  }

  @bean
  protected authorizationcodeservices authorizationcodeservices() {
    return new jdbcauthorizationcodeservices(datasource());
  }

  @bean
  public tokenstore tokenstore() {
    return new jdbctokenstore(datasource());
  }

  @override
  public void configure(clientdetailsserviceconfigurer clients) throws exception {
    // oauth_client_details
    clients.jdbc(datasource());
  }

  @override
  public void configure(authorizationserverendpointsconfigurer endpoints) {
    // oauth_approvals
    endpoints.approvalstore(approvalstore());
    // oauth_code
    endpoints.authorizationcodeservices(authorizationcodeservices());
    // oauth_access_token & oauth_refresh_token
    endpoints.tokenstore(tokenstore());
    // 支持password grant type
    endpoints.authenticationmanager(authenticationmanager);
  }

  @override
  public void configure(authorizationserversecurityconfigurer oauthserver) {
    oauthserver.allowformauthenticationforclients();
  }
}

oauth2 client 搭建

pom.xml

<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>com.github.scribejava</groupid>
  <artifactid>scribejava-apis</artifactid>
  <version>5.0.0</version>
</dependency>

defaultapi20

public class aiwanapi extends defaultapi20 {

  private string accesstokenendpoint = "http://localhost:8080/oauth/token";
  private string authorizationbaseurl = "http://localhost:8080/oauth/authorize";

  protected aiwanapi() {}

  private static class instanceholder {
    private static final aiwanapi instance = new aiwanapi();
  }

  public static aiwanapi instance() {
    return instanceholder.instance;
  }

  @override
  public string getaccesstokenendpoint() {
    return accesstokenendpoint;
  }

  @override
  protected string getauthorizationbaseurl() {
    return authorizationbaseurl;
  }
  
  @override
  public tokenextractor<oauth2accesstoken> getaccesstokenextractor() {
    return oauth2accesstokenjsonextractor.instance();
  }

  @override
  public oauth20service createservice(oauthconfig config) {
    return new aiwanservice(this, config);
  }

}

oauth20service

public class aiwanservice extends oauth20service {

  public aiwanservice(defaultapi20 api, oauthconfig config) {
    super(api, config);
  }
  
  @override
  protected oauthrequest createaccesstokenrequest(string code) {
    final oauthrequest request = new oauthrequest(getapi().getaccesstokenverb(), getapi().getaccesstokenendpoint());
    final oauthconfig config = getconfig();
    request.addparameter(oauthconstants.client_id, config.getapikey());
    final string apisecret = config.getapisecret();
    if (apisecret != null) {
      request.addparameter(oauthconstants.client_secret, apisecret);
    }
    request.addparameter(oauthconstants.code, code);
    request.addparameter(oauthconstants.redirect_uri, config.getcallback());
    final string scope = config.getscope();
    if (scope != null) {
      request.addparameter(oauthconstants.scope, scope);
    }
    request.addparameter(oauthconstants.grant_type, oauthconstants.authorization_code);
    request.addheader(oauthconstants.header,
        oauthconstants.basic + ' '
        + base64encoder.getinstance()
        .encode(string.format("%s:%s", config.getapikey(), apisecret).getbytes(charset.forname("utf-8"))));
    return request;
  }
}

获取access_token

@controller
public class indexcontroller {

  private static logger logger = loggerfactory.getlogger(indexcontroller.class);
  
  private static final string session_key_access_token = "my_access_token";

  /**
   * 为防止csrf跨站攻击,每次请求state的值应该不同,可以放入session!
   * 由于都是localhost测试,所以session无法保持,用一个固定值。
   */
  private static final string state = "secret-rensanning";
  private static final string client_id = "client";
  private static final string client_secret = "123456";
  private static final string callback_url = "http://localhost:8081/mall/callback";
  private static final string scope = "all";
  private oauth20service aiwanapi = new servicebuilder(client_id)
      .apisecret(client_secret)
      .scope(scope)
      .state(state)
      .callback(callback_url)
      .build(aiwanapi.instance());

  @getmapping("/")
  public string index() {
    return "index";
  }

  @getmapping("/signin")
  public void signin(httpservletrequest request, httpservletresponse response) throws ioexception {
    logger.debug("signin");
    logger.info("session id:{}", request.getsession().getid());
    string authorizationurl = aiwanapi.getauthorizationurl();
    logger.info("redirecturl:{}", authorizationurl);
    response.sendredirect(authorizationurl);
  }

  @getmapping("/callback")
  public string callback(@requestparam(value = "code", required = false) string code,
              @requestparam(value = "state", required = false) string state, httpservletrequest request) throws exception {

    logger.debug("callback [code:{}],[state:{}],[sessionid:{}]", code, state, request.getsession().getid());
    
    if (state.equals(state)) {
      logger.info("state ok!");
    } else {
      logger.error("state ng!");
    }

    oauth2accesstoken accesstoken = aiwanapi.getaccesstoken(code);
    request.getsession().setattribute(session_key_access_token, accesstoken);

    return "profile";
  }

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。