SSO登录验证设计要点细节(以微软 Microsoft SSO为例) 基于react python

流程 省流版

  • step 1
    • 用户访问前端页面
    • 前端检测无localStorage,Auth重定向到login页面
    • 点击按钮 输入账户密码
  • step 2
    • 后端接受账户密码 调用microsoft接口
    • 生成temp code返回前端
  • step 3
    • 前端使用temp code得到JWT存入localStorage
    • jiang进入正式界面

step 1 跳转

jsx 结构

js 复制代码
function App(){
	return(
		<>
		<AuthProvider> // 认证系统组件	
			<Router basename='/baseurl'>
				<Routes>
					<Route path='/login' element={<LoginPage/>}/>
					<Route paht='/auth/callback' element={<SSOCallback/>}/>
					<Route path='/landpage/:landpageId' element={
						<LayoutProvider>
							<AuthGuard>
								<LandPage/>
							</AuthGuard>
						</LayoutProvider>
					}/>
				</Routes>
			</Router>
		<AuthProvider>
		</>
	)
}
  • LoginPage 是前端登录页面的入口,用于给未登录的用户界面
  • callback是microsoft认证服务器认证回来的
    • 认证格式:/auth/callback/code=authorization_code
      • url 提取参数,调用后端 api/auth/callback?code=xxx
      • 后端验证code 返回jwt token
      • token存到localstorage
      • 跳到landpage
  • AuthGaurd包围需要展示的界面,执行如下代码
js 复制代码
const AuthGuard = ({children}) => {
	const {user, loading} = useAuth()
	if(loading) return <div>loading...</div>
	if(!user) return <Navigate to ='/login' replace/>

	return children
}

AuthProvider 初始化

这里补充一下user和loading从哪里来的,最外层包了一个AuthProvider 定义如下

js 复制代码
const AuthContext = createContext({
	user:null,
	loading:true,
	setUser:() => {},
	logout:() => {}
})

export const useAuth = () => {
	const context = useContext(AuthContext)
	return context
} 

//simplify hook reference you dont have to import AuthContexr and useContext at the same time
// if you do not use it
// you need to write 
// const {user} = useContext(AuthContext)

// now 
// const{user} = useAuth()

export const AuthProvider= ({children}) => {
	const [user, setUser] = useState(null)
	const [loading, setLoading] = useState(true)
	
	//init code
	useEffect() => {
		const init() = async() = > {
			// 情况1 检查是否sso回调
			const urlParams = new URLSearchParams(window.location.search)
			const tempCode = urlParams.get('temp_code')
			const error = urlParams.get('error')
			if(error) {return }
				// sso 回调会带有tempCode
			if(tempCode){
				const response = await authAPI.exchangeTempCode(tempCode)
				
				//保存authToken refreshToken user等信息
				localStorage.setItem('auth', response.auth)
				...
				setUser(response.user)
			}

			
			// 情况二 非回调 查询token
			const user = localStorage.getItem('user')
			const token = localStorage.getItem('token')
			if(user && token){
				const res = await authAPI.verify(token)
				if(res.success && res.valid){ // 没过期,设置
					setUset(user)
				}else{
					//过期 清空localstorage
					 		
				}
			} else{
				//情况3 第一次访问无session
			}
		
			

		}
		init()
		
		
	}, [])
	


	return(
		<AuthContext.Provider value = {{user,loading, setUser, logout}}
			{children}
		<AuthContext.Provider>
	)
}

执行完init,authGuard发现user没设置好,于是定向到login页面

step2 点击login之后的后端如何处理

py 复制代码
# /login 内部的框架
state = str(uuid.uuid4()) #
redirect_url = 'xxx' # 让microsoft认真完成之后重定向的url

# MSAL 
auth_url = self.msal_app.get_authorization_request_url(
	scopes=["User.Read"],
	redirect_url=redirect_url,
	state=state
) 

return auth_url

外层接收到microsft认证url 打包

py 复制代码
return RedirectResponse(url=auth_url, status_code=302)

用户输入账号密码给微软,搞定了之后微软跳转到redirect-url

step 3 回调

microsft回调后端

调用/api/auth/callback?code=xxx&state=xxx

py 复制代码
@router.get('/callback')
async def oauth_callback(code, state):
	result = await sso_service.handle_callback(code, env)
	temp_code = await sso_service.create_tmp_auth_code(result.user)
	return RedirectResponse(url=redirect_url, status_code=302)

sso_service.handle_callback 核心逻辑

py 复制代码
# 1. tmp code换token
result = self.msal_app.acquire_token_by_authorization_code(
	code=authorization_code,
	scopes=['User.Read'],
	redirect_url=redirect_url
) 

# 2得到access token id token和refresh token
id_token_claims = result.get('id_token_claims')
id = id_token_claims.get('oid')
email = id_token_claims.get('email')
name = id_token_claims.get('name')
# 3 本地database检查并创建用户 略过
......
# 4 JWT token 生成payload丢给jwt.encode
payload = {}
token = jwt.encode(payload, secretKey, algorithm)
refresh_token = jwt.encode(payload_for_refresh, secretKey, algorithm)

return UserInfo(
	id=id,
	name=name,
	email=email,
	token=token,
	refreshToken = refresh_token
)

create tmp code逻辑

py 复制代码
import secrets
tmp_code=secrets.token_urlsafe(32)
expiration=time.time()+600
self.tmp_auth_codes[tmp_code] = {
	'user_info':user_info, # 上一步的userInfo
	'expires_at':expiration,
	'create_at':time.time()
}
return tmp_code

重定向到前端(附带tmp code

/xxx?tmp_code=xxx

js 复制代码
const tmpCode=urlParams.get('tmp_code')
const res =  await. verifyTempCode(tmpCode)	//丢给后端验证 返回userinfo
	
localStorage.setItem(xxx)	//存起来res需要的东西 

获取temp code对应的数据

py 复制代码
# 检查是否存在
if tmp_code not in self.tmp_auth_codes:
	return None

auth = self.tmp_auth_codes[tmp_code]
if time.time() > auth['expires_at']:
	del self.tmp_auth_codes[tmp_code]
	return None


	userInfo = auth['user_info']
	self.tmp_auth_codes[tmp_code]
	return userInfo

# 检查是否过期

# 取出来 删除tmpcode,只用一次
相关推荐
超级大只老咪7 分钟前
数组的正向存储VS反向存储(Java)
java·开发语言·python
长安牧笛27 分钟前
心理健康情绪日记分析系统,用户输入文字日记后,AI提取情绪关键词,焦虑/愉悦等,生成周情绪波动曲线,并推荐调节建议。
python
艾上编程1 小时前
第三章——爬虫工具场景之Python爬虫实战:学术文献摘要爬取,助力科研高效进行
开发语言·爬虫·python
Hi_kenyon1 小时前
FastAPI+VUE3创建一个项目的步骤模板(二)
python·fastapi
拉普拉斯妖1081 小时前
DAY38 Dataset和DataLoader
python
Michelle80232 小时前
24大数据 16-1 函数复习
python
dagouaofei2 小时前
AI自动生成PPT工具对比分析,效率差距明显
人工智能·python·powerpoint
ku_code_ku2 小时前
python bert_score使用本地模型的方法
开发语言·python·bert
祁思妙想2 小时前
linux常用命令
开发语言·python
流水落花春去也3 小时前
用yolov8 训练,最后形成训练好的文件。 并且能在后续项目使用
python