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 结构

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包围需要展示的界面,执行如下代码
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 定义如下

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 只做log输出(或者啥也不做)
				//等路由处理user == null跳转到login界面
			}
		
			

		}
		init()
		
		
	}, [])
	


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

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

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

# /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 打包

return RedirectResponse(url=auth_url, status_code=302)

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

step 3 回调

microsft回调后端
调用/api/auth/callback?code=xxx&state=xxx

@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 核心逻辑

# 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逻辑

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

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

获取temp code对应的数据

# 检查是否存在
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,只用一次

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值