shiro 前後端分離框架 使用者登入解決方案
介紹
最近公司要原本springmvc+shiro 許可權控制的一個專案,改為前後端分離的,使前端人員能有更多時間來做前端的互動工作,提升使用者體驗。
但是這個問題就來了,原來沒有分離的情況下,shiro 許可權是通過憑證來登入的,現在需要更改為前端ajax提交登入資訊來登入,於是更改了shiro的登入方式,這裡做一個記錄。
@RequestMapping(value="/userLogin",method=RequestMethod.GET)
@ResponseBody
public ResultSuperApp superAppLogin(HttpServletRequest request) throws Exception{
String username = request.getParameter ("username");
String password = request.getParameter("password");
String error="未知錯誤異常";
if(null == username || null == password ){
return ResultSuperApp.getFailureInstance("引數為空", 300);
}
try {
Subject currentUser = SecurityUtils.getSubject ();
UsernamePasswordToken token = new UsernamePasswordToken(username,password, false,request.getRemoteAddr());
currentUser.login(token);
Subject subject = SecurityUtils.getSubject();
String sessionId = (String) subject.getSession().getId();
/*//無Redis伺服器,臨時性解決token
String Json = new Gson().toJson(token);
byte[] encodeBase64 = Base64.encodeBase64(Json.getBytes("UTF-8"));
String base64 = new String(encodeBase64);//Base64值*/
ActiveUser activeUser = ShiroUtil.getActiveUser();
return ResultSuperApp.getSuccess(activeUser, "登入成功", 200, sessionId);
//SecurityUtils.getSubject().getSession().setTimeout(-1000l);
} catch (Exception e) {
//根據與異常資訊丟擲對應的異常
if(e.getClass().getName()!=null){
if(UnknownAccountException.class.getName().equals(e.getClass().getName())){
//丟擲賬號不存在異常
error="賬號不存在";
}else if(IncorrectCredentialsException.class.getName().equals(e.getClass().getName())){
//
//throw new CustomException("密碼錯誤");
error = "使用者名稱密碼錯誤";
}else{
//密碼錯誤
//throw new CustomException("未知錯誤異常");
error = "未知錯誤異常";
}
}
return ResultSuperApp.getFailureInstance(error, 499);
}
}
注意:以上程式碼註釋掉的部分是返回登入的token資訊,之後每次前端請求的時候,引數都需要帶著這個token。
放開的程式碼片段是登入後,返回shiro的sessionid,前端在cookie裡儲存一下這個sessionid,每次請求的時候引數中都需要加上sessionid。
兩種方式都可以使用,但是本人推薦返回sessionid 的方式,因為前端給了sessionid 之後,我們只需要在服務中通過sessionid 獲取shiro的session資訊,然後就能獲取到shiro對應的使用者資訊。而使用token的這種方式的話,每次前端請求過來,後端都需要根據這個token再登入一下。兩者的區別就在這裡。
獲取使用者資訊(根據sessionid)
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.session.mgt.WebSessionKey;
import org.springframework.beans.factory.annotation.Autowired;
import com.eaju.bos.dao.mapper.DownloadCenterMapper;
import com.eaju.bos.dao.mapper.SonUserMapper;
import com.eaju.bos.dao.mapper.SysUserMapper;
import com.eaju.bos.entity.SysUser;
import com.eaju.bos.vo.ActiveUser;
import com.eaju.bos.vo.UserCustom;
/**
*
*/
public class ShiroUtil {
/**
*
* <p>description: 獲取ActiveUser並儲存至session中一份</p>
* @return
* @date 2016年8月15日 下午3:37:23
* @author MrDuan
*/
public static ActiveUser getActiveUser(){
//從shiro的session中取出activeUser
Subject subject = SecurityUtils.getSubject();
//取出身份資訊
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
if(activeUser!=null){
Session session = subject.getSession();
ActiveUser user = (ActiveUser) session.getAttribute("user");
if(user==null){
session.setAttribute("user", activeUser);
}
return activeUser;
}else{
return null;
}
}
/**
* 根據sessionid 獲取使用者資訊
* @param sessionID
* @param request
* @param response
* @return
*/
public static ActiveUser getActiveUser(String sessionID,HttpServletRequest request,HttpServletResponse response) throws Exception{
boolean status = false;
SessionKey key = new WebSessionKey(sessionID,request,response);
Session se = SecurityUtils.getSecurityManager().getSession(key);
Object obj = se.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
//org.apache.shiro.subject.SimplePrincipalCollection cannot be cast to com.hncxhd.bywl.entity.manual.UserInfo
SimplePrincipalCollection coll = (SimplePrincipalCollection) obj;
ActiveUser activeUser = (ActiveUser)coll.getPrimaryPrincipal();
if(activeUser!=null){
ActiveUser user = (ActiveUser) se.getAttribute("user");
if(user==null){
se.setAttribute("user", activeUser);
}
return activeUser;
}else{
return null;
}
}
}
sessionid 方式 Controller裡獲取使用者資訊
ActiveUser activeUser = null ;
try {
activeUser = ShiroUtil.getActiveUser(token, request, response);
} catch (Exception e1) {
if(UnknownSessionException.class.getName().equals(e1.getClass().getName())){
//丟擲使用者獲取失敗異常
retJsono.put("info", "使用者獲取失敗!");
retJsono.put("returnCode",1000);
return retJsono;
}else{
retJsono.put("info", "內部錯誤!");
retJsono.put("returnCode",500);
return retJsono;
}
}
注意: 這裡是舉個例子,獲取方式就是這個樣子,ActiveUser 是你們自己存在shiro session裡的使用者實體,大家自己修改為自己的就可以了。
獲取使用者資訊(token方式,每次請求過來都需要登入一下,不太推薦)
//登入獲取使用者資訊
private ActiveUser loginToken(HttpServletRequest request) {
String token = request.getParameter("token");
Subject currentUser = SecurityUtils.getSubject();
byte[] decodeBase64 = Base64.decodeBase64(token);
String stringtoken = new String(decodeBase64);//Base64值
currentUser.login(new Gson().fromJson(stringtoken, UsernamePasswordToken.class));
ActiveUser activeUser = ShiroUtil.getActiveUser();
return activeUser;
}
為什麼這麼做?
因為前後端分離後,前後端可能會部署在不同的伺服器上面,會跨域,前端每次請求時,都會是一次新的請求,所以前端需要記住一個使用者的標識,每次請求資料都傳給後端。後端才能知道是哪個使用者。
以上均為個人想法,大家有好的意見或者建議,我會修改。