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,只用一次
相关推荐
shughui12 分钟前
Python基础面试题:语言定位+数据类型+核心操作+算法实战(含代码实例)
开发语言·python·算法
No0d1es23 分钟前
2025年12月电子学会青少年软件编程Python六级等级考试真题试卷
开发语言·python·青少年编程·等级考试·电子学会
Blossom.11827 分钟前
Transformer架构优化实战:从MHA到MQA/GQA的显存革命
人工智能·python·深度学习·react.js·架构·aigc·transformer
溪海莘1 小时前
如何部署使用uv管理依赖的python项目 ?
开发语言·python·uv
我送炭你添花1 小时前
Python与串口:从基础到实际应用——以Pelco KBD300A模拟器项目为例
开发语言·python·自动化·运维开发
鹏多多1 小时前
jsx/tsx使用cssModule和typescript-plugin-css-modules
前端·vue.js·react.js
高洁011 小时前
CLIP 的双编码器架构是如何优化图文关联的?(2)
python·深度学习·机器学习·知识图谱
m0_626535201 小时前
快速排序学习 l方法 h方法
开发语言·python
brent4232 小时前
DAY49 预训练模型
python
清水白石0082 小时前
深入 Python 的底层世界:从 C 扩展到 ctypes 与 Cython 的本质差异全解析
c语言·python·neo4j