主要说明:
基于若依springcloud微服务框架的2.1版本
嫌弃缩进不舒服的,直接访问我的博客站点:
http://binarydance.top//aticle_view.html?aticle_id=603216759344082944&t=1623984554005
一开始网上教程一堆,都是各抄各的,有的直接代码缺少,有的直接不可以用(MLGB的),于是乎去spring官网找找看,还真找到,最终自己配置搭建成功跑了一遍demo,美滋滋。一些HTML文件还是网上的,见谅~
由于是springcloud项目(注册和配置中心是nacos),认证中心在auth模块,自己demo搭建测试直接在auth模块,没有走网关gateway
目前测试的scope只有一个:server,见表:sys_oauth_client_details
sys_oauth_client_details表:
CREATE TABLE `sys_oauth_client_details` (
`client_id` varchar(255) NOT NULL COMMENT '终端编号',
`resource_ids` varchar(255) DEFAULT NULL COMMENT '资源ID标识',
`client_secret` varchar(255) NOT NULL COMMENT '终端安全码',
`scope` varchar(255) NOT NULL COMMENT '终端授权范围',
`authorized_grant_types` varchar(255) NOT NULL COMMENT '终端授权类型',
`web_server_redirect_uri` varchar(255) DEFAULT NULL COMMENT '服务器回调地址',
`authorities` varchar(255) DEFAULT NULL COMMENT '访问资源所需权限',
`access_token_validity` int(11) DEFAULT NULL COMMENT '设定终端的access_token的有效时间值(秒)',
`refresh_token_validity` int(11) DEFAULT NULL COMMENT '设定终端的refresh_token的有效时间值(秒)',
`additional_information` varchar(4096) DEFAULT NULL COMMENT '附加信息',
`autoapprove` tinyint(4) DEFAULT NULL COMMENT '是否登录时跳过授权',
`origin_secret` varchar(255) NOT NULL COMMENT '终端明文安全码',
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='终端配置表';
INSERT INTO `sys_oauth_client_details` VALUES ('web', '', '$2a$10$t.Hw9Nu.lWp5iK9I7MZRCuX9EeV2DNu6xj9wunfD5ZclvQSdoBo5O', 'server', 'password,refresh_token,authorization_code,implicit,client_credentials', 'https://www.baidu.com', NULL, 604800, 1209600, NULL, NULL, '123456');
Spring Security Oauth2 的maven坐标:
<!-- Spring Security Oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
项目配置:
# Tomcat
server:
port: 9200
# Spring
spring:
application:
# 应用名称
name: my-auth
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 192.168.1.39:8848
config:
# 配置中心地址
server-addr: 192.168.1.39:8848
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
main:
allow-bean-definition-overriding:
true
引入模板解析引擎:
<!-- thymeleaf 模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
AuthorizationServerConfigurerAdapter无需改动,你的是什么就是什么:
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
// 无需改动,你的是什么就是什么
......
}
上面是基础,关键来了:
resources下面建立static目录,放置登录页面(注意:form表单action="/login"):base-login.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">
<div class="form-container">
<form class="form-signin" method="post" action="/login">
<h2 class="form-signin-heading">用户登录</h2>
<p>
<label for="username" class="sr-only">用户名</label>
<input type="text" id="username" name="username" class="form-control" placeholder="用户名" required autofocus>
</p>
<p>
<label for="password" class="sr-only">密码</label>
<input type="password" id="password" name="password" class="form-control" placeholder="密码" required>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">登 录</button>
</form>
</div>
</div>
</body>
</html>
配置WebSecurityConfigurerAdapter:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Security 安全认证相关配置
*/
@Order(99)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置修改hideUserNotFoundExceptions = false,不隐藏usernameNotFundExceptions
* @return
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/base-login.html") //自定义的登录页面 **重要**
.loginProcessingUrl("/login") //原始的处理登录的URL,保持和base-login.html的form表单的action一致 **重要**
.permitAll() //放开 **重要**
.and()
.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")// **重要**
.and()
.authorizeRequests()
.antMatchers(
"/xxx/**",).permitAll()//一些需要放开的URL
.anyRequest().authenticated()
.and().headers().frameOptions().disable()
.and().csrf().disable();
}
}
到此登录页面自定义完成。别急,还有自定义授权页面呢。
在resources目录下面建立templates目录,放置授权页面(注意:form表单action="/oauth/authorize"):base-grant.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>授权</title>
</head>
<style>
html{
padding: 0px;
margin: 0px;
}
.title {
background-color: #E9686B;
height: 50px;
padding-left: 20%;
padding-right: 20%;
color: white;
line-height: 50px;
font-size: 18px;
}
.title-left{
float: right;
}
.title-right{
float: left;
}
.title-left a{
color: white;
}
.container{
clear: both;
text-align: center;
}
.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;
}
</style>
<body style="margin: 0px">
<div class="title">
<div class="title-right">oauth2授权</div>
<div class="title-left">
<a href="#help">帮助</a>
</div>
</div>
<div class="container">
<h3 th:text="${clientId}+' 请求授权,该应用将获取你的以下信息'"></h3>
<p>昵称,头像和性别</p>
授权后表明你已同意 <a href="#boot" style="color: #E9686B">服务协议</a>
<form method="post" action="/oauth/authorize">
<input type="hidden" name="user_oauth_approval" value="true">
<div th:each="item:${scopes}">
<input type="radio" th:name="'scope.'+${item}" value="true" checked>同意
<input type="radio" th:name="'scope.'+${item}" value="false" >拒绝
</div>
<input name="authorize" value="同意/授权" type="submit">
</form>
</div>
</body>
</html>
同时,controller覆盖oauth2的方法:
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
@Controller
//必须配置
@SessionAttributes("authorizationRequest")
public class BootGrantController {
@RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
ModelAndView view = new ModelAndView();
view.setViewName("base-grant"); //自定义页面名字,resources\templates\base-grant.html
view.addObject("clientId", authorizationRequest.getClientId());
view.addObject("scopes",authorizationRequest.getScope());
return view;
}
}
至此,自定义登录页面、授权页面完成。
①获取授权码code(注意:client_id=web):
http://192.168.1.39:9200/oauth/authorize?response_type=code&client_id=web&redirect_uri=https://www.baidu.com&scope=server
第一次访问上面地址获取授权码code,先会跳转至自定义登录页面:
输入账号密码,跳转至授权页面:
同意之后,跳转到我们设定的redirect_uri=https://www.baidu.com,同时获取授权码code(牛逼,这样图片也违规,去我博客站点看吧~):
②根据上面获取的授权码code=rLePQ8获取访问的token:
至此流程完成,等到我们的token
③根据token去访问你需要的资源吧。
特别说明的是:第一次授权以后获取授权码code,之后获取授权码code直接收入账号、密码后立即返回授权码code,并不会再一次要求用户进入授权页面(base-grant.html)允许、拒绝选择操作。
如果希望默认就是授权,不需要跳出授权页面(base-grant.html),那么sys_oauth_client_details表中,autoapprove(是否登录时跳过授权)设置为true(值:1)即可。
Spring Security Oauth2 授权码模式下 自定义登录、授权页面(演示)