前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据。前后端之间完全通过public API约定。
Shiro从Realm获取安全数据(如用户、角色、权限):就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
doGetAuthorizationInfo()方法用于控制用户权限获取。
doGetAuthenticationInfo()方法用于控制用户登录。
在项目包下建一个ShiroRealm类,继承AuthorizingRealm抽象类。
public class ShiroRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(ShiroRealm.class);
@Autowired
MemberService memberService;
@Autowired
PermissionService permissionService;
/**
* 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set roles = Sets.newHashSet();
Set permissions = Sets.newHashSet();
List pIdList =null;
try
{
pIdList = permissionService.findPermissionIdByMemberId(UserUtils.getCurrrentUserId());
}catch(Exception e)
{
e.printStackTrace();
}
if (!GeneralUtil.isEmpty(pIdList))
{
for (int pid: pIdList)
{
permissions.add(pid+"");
}
}
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
/**
* 在这个方法中,进行身份验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//CaptchaUsernamePasswordToken token = (CaptchaUsernamePasswordToken) authenticationToken;
String username = (String) authenticationToken.getPrincipal();
String captcha = null;
Object obj_captcha = SecurityUtils.getSubject().getSession()
.getAttribute("captchaCode");
String ip = null;
Object obj_ip = SecurityUtils.getSubject().getSession()
.getAttribute("captchaCodeIp");
if (obj_ip instanceof String) {
ip = (String) obj_ip;
}
Member member =this.memberService.findUserByUserLoginName(username);
if (member == null) {
return null;
}
return new SimpleAuthenticationInfo(
//new ShiroUser(user.getId(), user.getLoginName(), user.getLoginName()),
new ShiroUser(member.getId().longValue(), member.getUserName(), member.getUserName()),
member.getPassword(), //密码
new SimpleByteSource(member.getUserName()),//salt=username
getName() //realm name
);
}
}
/view/** = anon
/unauth=anon
/login=anon
/logout = logout
/api/** = anon
/error = anon
/resources/** = anon
/validateTicket = anon
/** = user
@RequestMapping(value = "login", method = RequestMethod.POST)
@ResponseBody
public ResultBean userLogin(HttpServletRequest request, @RequestBody UserBean bean) {
JSONObject jsonObject = new JSONObject();
ResultBean resultBean=new ResultBean();
String username=bean.getUsername();
String password=bean.getPassword();
logger.info(" 我进来了");
logger.debug("scheme is {}", request.getScheme());
if (null != SecurityUtils.getSubject().getPrincipal()) {
SecurityUtils.getSubject().logout();
}
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
jsonObject.put("token", subject.getSession().getId());
LoginInfo loginInfo=new LoginInfo();
loginInfo.setLoginName(username);
loginInfo.setLoingIp(request.getRemoteAddr());
loginInfo.setOperatingResults("0");
loginInfo.setLoginTime(new Date());
loginInfo.setLoginStatus("1");
this.loginInfoService.save(loginInfo);
} catch (IncorrectCredentialsException e) {
CommonBeanUtil.makeFailMessage(resultBean);
jsonObject.put("msg", "密码错误");
} catch (LockedAccountException e) {
CommonBeanUtil.makeFailMessage(resultBean);
jsonObject.put("msg", "登录失败,该用户已被冻结");
} catch (AuthenticationException e) {
CommonBeanUtil.makeFailMessage(resultBean);
jsonObject.put("msg", "该用户不存在");
} catch (Exception e) {
e.printStackTrace();
}
resultBean.setResult(jsonObject);
logger.info("response data:"+jsonObject.toString());
return resultBean;
}
@RequestMapping(value = "/unauth")
@ResponseBody
public ResultBean unauth() {
ResultBean resultBean=new ResultBean();
CommonBeanUtil.makeFailMessage(resultBean);
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", "请重新登录");
resultBean.setResult(jsonObject);
return resultBean;
}
shiro框架实现了退出方法,然后跳转到登录页面,这个在前后端分离中就不太合适了,需要修改
public class ForceLogoutFilter extends AccessControlFilter {
private static final Logger log = LoggerFactory.getLogger(ForceLogoutFilter.class);
@Autowired
private LoginInfoServiceImpl loginInfoService;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Session session = getSubject(request, response).getSession(false);
String id=session.getId().toString();
if(session == null) {
return true;
}
return session.getAttribute(Constants.SESSION_FORCE_LOGOUT_KEY) == null;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
try {
//强制退出
getSubject(request, response).logout();
} catch (Exception e) {/*ignore exception*/}
String loginUrl = getLoginUrl() + (getLoginUrl().contains("?") ? "&" : "?") + "forceLogout=1";
WebUtils.issueRedirect(request, response, loginUrl);
return false;
}
/**
*
* @Description: 自定义退出的拦截器,退出返回json
* shiro框架实现了退出方法,然后跳转到登录页面,这个在前后端分离中就不太合适了,需要修改
*
* @param:
* @return:
* @auther: chengguangbing
* @date: 2019-03-14 10:57
*/
@Override
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
// BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
// String str = UserUtils.getCurrrentUserName();
// String wholeStr = "";
// while((str = reader.readLine()) != null){//一行一行的读取body体里面的内容;
// wholeStr += str;
// }
// UserBean bean=JSONObject.parseObject(wholeStr,UserBean.class);
Subject subject = getSubject(request, response);
ResultBean resultBean=new ResultBean();
JSONObject jsonObject = new JSONObject();
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
if(subject.isAuthenticated()){
try {
String username=UserUtils.getCurrrentUserName();
subject.logout();
LoginInfo loginInfo=new LoginInfo();
loginInfo.setLoginName(username);
loginInfo.setLoingIp(request.getRemoteAddr());
loginInfo.setOperatingResults("1");
loginInfo.setLogoutTime(new Date());
loginInfo.setLoginStatus("0");
this.loginInfoService.save(loginInfo);
} catch (SessionException ise) {
log.debug("Encountered session exception during logout. This can generally safely be ignored.", ise);
}
jsonObject.put("msg", "退出成功");
}else{
jsonObject.put("msg", "用户没有登录,不用退出");
}
resultBean.setCode(WebConfig.LOGOUT_CODE);
resultBean.setResult(jsonObject);
out.println(JSONObject.toJSONString(resultBean));
out.flush();
out.close();
return false;
}
}
拦截那些未执行登录的请求,返回json格式的响应。
public class SysUserFilter extends UserFilter {
private final static Logger LOGGER = LoggerFactory.getLogger(SysUserFilter.class);
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse res = (HttpServletResponse)response;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setStatus(HttpServletResponse.SC_OK);
res.setCharacterEncoding("UTF-8");
PrintWriter writer = res.getWriter();
ResultBean resultBean=new ResultBean();
CommonBeanUtil.makeFailMessage(resultBean);
resultBean.setCode(WebConfig.LOGOUT_CODE);
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", "请重新登录");
resultBean.setResult(jsonObject);
writer.write(JSON.toJSONString(resultBean));
writer.close();
return false;
}
}
经过查找发现定义的filter必须满足filter instanceof AuthorizationFilter,只有perms,roles,ssl,rest,port才是属于AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter,所以unauthorizedUrl设置后页面不跳转,解决方法要么就使用perms,roles,ssl,rest,port,要么自己配置异常处理,进行页面跳转。 这里选择自定义异常处理。处理全局异常。
public class GlobalExceptionResolver implements HandlerExceptionResolver {
private final static Logger LOGGER = LoggerFactory.getLogger(cn.suyan.exceptions.GlobalExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
LOGGER.debug("Catch exception Url :{}, Method: {}, Exception: {}", request.getRequestURL(), request.getMethod(), ExceptionUtils.getMessage(ex));
ex.printStackTrace();
return returnJson(request, response, ex);
}
private ModelAndView parsePost(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if(isAjax(request)){
return returnJson(request, response, ex);
}else {
return returnView(request,ex);
}
}
public static ModelAndView returnJson(HttpServletRequest request, HttpServletResponse response, final Exception ex) {
ModelAndView modelAndView = new ModelAndView();
try {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(HttpServletResponse.SC_OK);
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
ResultBean resultBean=new ResultBean();
CommonBeanUtil.makeFailMessage(resultBean);
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", getExMessage(ex));
resultBean.setResult(jsonObject);
writer.write(JSON.toJSONString(resultBean));
writer.close();
}catch (Exception e){
}
return modelAndView;
}
public static ModelAndView returnView(HttpServletRequest request, Exception ex) {
ModelAndView modelAndView = new ModelAndView();
StringWriter sw = new StringWriter();
ex.printStackTrace(new PrintWriter(sw, true));
modelAndView.setViewName("error.jsp");
request.setAttribute("errorMsg", getExMessage(ex));
request.setAttribute("errorprint", sw.toString());
return modelAndView;
}
private static String getExMessage(Exception ex) {
if (ex instanceof UnauthorizedException) {
return "无权限调用";
}
return ex.getMessage();
}
}
传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中,我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。自定义ShiroSessionManager类继承DefaultWebSessionManager类,重写getSessionId方法。
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 获取请求头中key为“Authorization”的value == sessionId
*/
private static final String AUTHORIZATION ="Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "cookie";
/**
* @Description shiro框架 自定义session获取方式
* 可自定义session获取规则。这里采用ajax请求头 {@link AUTHORIZATION}携带sessionId的方式
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// TODO Auto-generated method stub
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (StringUtils.isNotEmpty(sessionId)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
}
return super.getSessionId(request, response);
}
}
8 ResultBean
public class ResultBean {
private String message = WebConfig.SUCCESS_MESSAGE;//自定义常量
private String code="";
private String status = WebConfig.RESULT_SUCCESS;//自定义常量
private Object result = new JSONObject();
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}