SpringMVC整合shiro、自定义sessionManager实现前后端分离

前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据。前后端之间完全通过public API约定。

1 自定义Realms

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
        );
    }
     }

2Shiro的配置文件




    
    
        
    

    
    
        
        
        
        
    

    
    
        
        
        
        
        
        
    
    
    

    
    
        
        
        
    

    
        
        
        
    

    
    
        
        
        
    

    
    
        
        
    

    
    
        
        
    

    
    
        
        
        
        
        
        
        
    

    
    
        
            
                
            
        
        
        
        
    
    
    
        
        
    


    
    
    
        
    
    
    

    
    
        
        
        
        
            
                
                
            
        
        
            
                
                /view/** = anon
                
                /unauth=anon
                /login=anon
                /logout = logout
                /api/** = anon
                /error = anon
                /resources/** = anon
                /validateTicket = anon
                /** = user
                
            
        
    

    
    


3修改我们的Controller中的登录请求

@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;
    }

4 自定义退出拦截器

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;

    }
}

5 自定义拦截器

拦截那些未执行登录的请求,返回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;
	}
}

6自定义异常

经过查找发现定义的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();
	}
	
}

7自定义sessionManager

传统结构项目中,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;
        }
    }

你可能感兴趣的:(java)