详解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模块)
效果图
himall.gif
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"; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。