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,只用一次
相关推荐
从零开始学习人工智能2 小时前
【踩坑实录】WSL2 解决 onnxruntime\-gpu ImportError: libcudart\.so\.13 无 CUDA13 运行库问题
python
抱抱宝3 小时前
Agent-study项目教程(03):手写 Mini-ReAct Agent(不依赖框架)
javascript·人工智能·gpt·react.js·prompt·agent
卷无止境4 小时前
在 awesome-fastapi 里,哪些库值得一看?
后端·python
zhanghaha13144 小时前
Python进阶教程:6_JSON 数据解析 —— 新手完全指南
开发语言·python·json
Python私教4 小时前
API 输出模型怎么设计:从 model_dump() 到显式展示层
python·fastapi
Python私教5 小时前
本地 AI 工具服务该绑定 127.0.0.1 还是 0.0.0.0?
python·fastapi
卷无止境5 小时前
FastAPI 的Admin面板生态
后端·python
ctlover5 小时前
Streamlit 框架
python
ι:6 小时前
MATLAB 与 Python 搭建无人机地面站:优势、劣势与选型逻辑
python·matlab·无人机
船厂电气自动化ai大模型6 小时前
AI大模型与数学 第32课 函数凹凸性与二阶导数:拐点求解、凹凸区间计算(10道二阶导数计算题)
数据结构·人工智能·python·深度学习·算法