流程 省流版
- 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
- 认证格式:/auth/callback/code=authorization_code
- 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,只用一次