基于jeesite开发独立用户模块

Jeesite 独立用户模块开发

开发需求:Jeesite登录模块添加用户类型选择,并且多添加一个前段角色,验证并授权,允许前段角色登录

  1. 单独建立一个前端用户管理模块与框架中的用户模块区分,前端登陆页设置用户类型作为区分登陆名归属与前段用户管理模块或框架中的用户模块。
  2. 前端用户登陆校验方式与框架系统用户登陆校验方式一致。
  3. 授权前端用户角色。(未完成)

数据表

 使用的数据库为MySql,拓展数据库管理工具为Navicat

 在Jeesite搭建完成的基础上,首先从数据库表的建设开始,后台用户表已经有了,这里建立的是前台用户表,前后用户分离,参照后台用户表的字段信息。

前台用户表 front_user

基于jeesite开发独立用户模块_第1张图片基于jeesite开发独立用户模块_第2张图片

省略代码生成部分

前后台路径设置分析及框架原设计原理

路径设置分析

提示,输入以下几个地址尝试访问:

  • http://localhost/项目名 (默认首页地址)
  • http://localhost/项目名/a (后台管理首页地址)
  • http://localhost/项目名/f (前台首页地址)

 输入“http://localhost/项目名”, 会直接跳转到项目工程的默认首页,一般在web.xml中设定对应的首页,但这里通过spring-mvc.xml的81行可见设置,默认首页映射到jeesite.properties的web.view.index属性中,所以,我们直接配置这个属性就可以完成默认首页的设置。


 接下来,我们看一下原设定是什么,web.view.index属性原设定值为“/a”,“/a”是什么,它是“后台管理”路径的前缀路径,用来区分前后台的路径,我们在默认登录的时候会进入到“/a/login”的路径中,这个值“/a”对应的属性为jeesite.properties的“adminPath”,参照在controller层中类文件可见,映射路径@RequestMapping中必带“${adminPath}”参数,所以我们可以确定,凡是带了该参数的均为后台路径(jeesite.properties源代码模式下unicode编码已经有说明了)。

adminPath=/a
frontPath=/f
web.view.index=/a

 所以实际的流程是这样的,通过输入“http://localhost/项目名”地址访问, 会跳转到web.view.index设定的首页地址(这里以原设定“/a”为例),所以访问的实际地址为“http://localhost/项目名/a”, 而这个地址对应的controller层方法是在“com.thinkgem.jeesite.modules.sys.web.LoginController”类文件中,对应的方法为“index”。

/**
 * 登录成功,进入管理首页
 */
@RequiresPermissions("user")
@RequestMapping(value = "${adminPath}")
public String index(HttpServletRequest request, HttpServletResponse response) {
}

 输入“http://localhost/项目名”或“http://localhost/项目名/a”会来到这个方法, 但是打印输出发现并没有进来,而且最终地址为“http://localhost/项目名/a/login”, 是因为在进入这个方法前要进行shiro权限校验,如果没通过是无法进入会跳转到指定的地址,参照对比spring-context-shiro.xml,即可发现。



	
		
			/test/** = anon
			/static/** = anon
			/userfiles/** = anon
			${adminPath}/cas = cas
			${adminPath}/login = authc
			${adminPath}/logout = logout
			${adminPath}/** = user
			/act/editor/** = user
			/ReportServer/** = user
		
	




	
	
	
	
        
            
            
        
    
	
		
	

 进入“index”方法需要“user”权限(进行shiro权限校验),否则会跳转到“loginUrl”对应的方法“${adminPath}/login”,即“http://localhost/项目名/a/login”, 至此后台的登录流程大概。

框架原设计原理

sysLogin.jsp登录:

 整个jsp页面其实就是一个表单,它的主要目的就是接受用户输入的用户名和密码字段,然后交给后台处理。action变量指定了该表达的提交方式,既是交由/a/login所对应的函数来处理。


 表单里的两个属性,一个属性名为username,一个名为password,表单会借由request属性传到函数当中,届时就能通过getUsername和getPassword两个函数从request中取出。这部分是在FormAuthenticationFilter中的createToken函数中实现。

shiro

 jsp将username和password打包扔给后台,那么后台是由什么接受呢?在spring-mvc中,负责接受前台数据的是controller部分,而form中所指定的action是/a/login。

LoginController:第三步

/**
 * 管理登录
 */
@RequestMapping(value = "${adminPath}/login", method = RequestMethod.GET)
public String login(HttpServletRequest request, HttpServletResponse response, Model model) {
	Principal principal = UserUtils.getPrincipal();
	
	if (logger.isDebugEnabled()){
		logger.debug("login, active session size: {}", sessionDAO.getActiveSessions(false).size());
	}
	
	// 如果已登录,再次访问主页,则退出原账号。
	if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){
		CookieUtils.setCookie(response, "LOGINED", "false");
	}
	
	// 如果已经登录,则跳转到管理首页
	if(principal != null && !principal.isMobileLogin()){
		return "redirect:" + adminPath;
	}
}

/**
 * 登录失败,真正登录的POST请求由Filter完成
 */
@RequestMapping(value = "${adminPath}/login", method = RequestMethod.POST)
public String loginFail(HttpServletRequest request, HttpServletResponse response, Model model) {

    // 通过 tpye 类型来区分实例化 Principal 是 User 或者 FrontUser
	String type = request.getParameter("type").toString();

	Principal principal;

	if(type == "0"){
		 principal = UserUtils.getPrincipal();
	}else {
		 principal = FrontUserUtils.getPrincipal();
	}

	//Principal principal = UserUtils.getPrincipal();
	
	// 如果已经登录,则跳转到管理首页
	if(principal != null){
		return "redirect:" + adminPath;
	}

	String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);


	boolean rememberMe = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM);
	boolean mobile = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_MOBILE_PARAM);
	String exception = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
	String message = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM);
	
	if (StringUtils.isBlank(message) || StringUtils.equals(message, "null")){
		message = "用户或密码错误, 请重试.";
	}
	model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, username);
	model.addAttribute(FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM, rememberMe);
	model.addAttribute(FormAuthenticationFilter.DEFAULT_MOBILE_PARAM, mobile);
	model.addAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, exception);
	model.addAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM, message);
	
	if (logger.isDebugEnabled()){
		logger.debug("login fail, active session size: {}, message: {}, exception: {}", 
				sessionDAO.getActiveSessions(false).size(), message, exception);
	}
	return "modules/sys/sysLogin";
}

  这是因为shiro的登陆功能在controller之前加入了一个filter。这个filter被配置在文件spring-context-shiro.xml文件里。






    
        
    


    
        /static/** = anon
        /userfiles/** = anon
        ${adminPath}/login = authc
        ${adminPath}/logout = logout
        ${adminPath}/** = user
    

  以上就是配置过程最关键的部分。loginuUrl属性所指定的url表示的是所有未通过验证的url所访问的位置,此处就是登录界面;successUrl表示的是成功登陆的url访问的位置,此处就是主页。filters则是配置具体验证方法的位置。在此处,${adminPath}/login = authc指定了/a/login,既登陆页面,所需要的验证权限名为authc,又配置了authc所用的filter为formAuthenticationFilter。

  因此整个逻辑是:如果任何地方未登陆,则访问/a/login页面,而/a/login页面的验证权限中又指定了formAuthenticationFilter做为过滤,如果过滤中验证成功,则访问/a这个主页。所以,login.jsp中的表单信息则首先交由formAuthenticationFilter首先处理。

  formAuthenticationFilter中的处理,需要关注的类主要在com.thinkgem.jeesite.modules.sys.security这个包里。通常FormAuthenticationFilter是主要逻辑管理类,SystemAuthorizingRealm这个类则是数据处理类,相当于DAO。但是直接从代码里没发看出功能,这是因为jeesite中的这两个类都是继承于shiro的类。

  大致来讲,首先表单的request被formAuthenticationFilter接收到,然后传给createToken函数,该函数从request中取出name和password,然后生成自定义的一个token传给SystemAuthorizingRealm中的doGetAuthenticationInfo验证。SystemAuthorizingRealm中有systemService的实例,该实例含有userDAO能取出数据库中的name和password。接着由这两个密码生成SimpleAuthenticationInfo,再由info中的逻辑来验证。以上过程中jeesite实现的代码分别如下:

FormAuthenticationFilter:第一步

// TODO 设置 tyoe 常量
public static final String DEFAULT_TYPE = "type";

// TODO 设置 tyoe 常量
private String typeParam = DEFAULT_TYPE;

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
	String username = getUsername(request);
	String password = getPassword(request);

	// TODO 通过请求域获得 type
	// 通过请求域获得 type
	String type = getType(request);

	if (password==null){
		password = "";
	}
	boolean rememberMe = isRememberMe(request);
	String host = StringUtils.getRemoteAddr((HttpServletRequest)request);
	String captcha = getCaptcha(request);
	boolean mobile = isMobileLogin(request);
	//返回一个 UsernamePasswordToken 令牌对象进行校验
	return new UsernamePasswordToken(username, password.toCharArray(),type, rememberMe, host, captcha, mobile);
}

/**
 * 获取登录用户名
 */
protected String getUsername(ServletRequest request, ServletResponse response) {
	String username = super.getUsername(request);
	if (StringUtils.isBlank(username)){
		username = StringUtils.toString(request.getAttribute(getUsernameParam()), StringUtils.EMPTY);
	}

	System.out.println(" 11111 ========================================================"+username);

	return username;
}

/**
 * 获取登录密码
 */
@Override
protected String getPassword(ServletRequest request) {
	String password = super.getPassword(request);
	if (StringUtils.isBlank(password)){
		password = StringUtils.toString(request.getAttribute(getPasswordParam()), StringUtils.EMPTY);
	}

	System.out.println(" 11111 ========================================================"+password);

	return password;
}

// TODO 获取用户类型
/**
 * 获取用户类型
 */
protected String getType(ServletRequest request){
	String type = this.getTypeParam(request);
	if (StringUtils.isBlank(type)){
		type = StringUtils.toString(request.getAttribute(getTypeParam()),StringUtils.EMPTY);
	}

	System.out.println(" 11111 ========================================================"+type);

	return type;
}

// TODO TypeParam 的 get 方法
public String getTypeParam(){
	return typeParam;
}

// TODO TypeParam 的 get 方法(带 ServletRequest 请求对象 )
protected String getTypeParam(ServletRequest request){
	return WebUtils.getCleanParam(request,getTypeParam());
}

UsernamePasswordToken:

// TODO 设置用户类型 type 
private String type;

// TODO 带用户类型 type 的构造器
public UsernamePasswordToken(String username, char[] password,
        String type, boolean rememberMe, String host, String captcha, boolean mobileLogin) {
	super(username, password, rememberMe, host);
	this.type = type;
	this.captcha = captcha;
	this.mobileLogin = mobileLogin;
}

// TODO 
public String getType() {
    return type;
}

// TODO
public void setType(String type) {
    this.type = type;
}

SystemService:

//TODO 根据登录名获取前端用户
/**
 * 根据登录名获取前端用户
 * */
public CampusinfoFrontUser getFrontUserByLoginName(String loginName){
	return FrontUserUtils.getByFrontLoginName(loginName);
}

SystemAuthorizingRealm:第二步

/**
 * 认证回调函数, 登录时调用
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
	UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

	int activeSessionSize = getSystemService().getSessionDao().getActiveSessions(false).size();
	if (logger.isDebugEnabled()){
		logger.debug("login submit, active session size: {}, username: {}", activeSessionSize, token.getUsername());
	}

	// 校验登录验证码
	if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){
		Session session = UserUtils.getSession();
		String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE);
		if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){
			throw new AuthenticationException("msg:验证码错误, 请重试.");
		}
	}

	System.out.println(" 22222 ========================================================"+ token.getType());

	String type = token.getType();

	// 判断用户类型
	if(type.equals("0")){
		// 校验后端用户名密码
		User user = getSystemService().getUserByLoginName(token.getUsername());
		if (user != null) {
			// 校验后台用户是否允许登陆
			if (Global.NO.equals(user.getLoginFlag())){
				throw new AuthenticationException("msg:该已帐号禁止登录.");
			}

			byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
			return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()),
					user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
		} else {
			return null;
		}
	} else{
		System.out.println("  campusinfoFrontUser  +++++++++++++++++++++++++++++++++++++++++++++++++++++");
		// 校验前端用户名密码
		CampusinfoFrontUser campusinfoFrontUser = getSystemService().getFrontUserByLoginName(token.getUsername());
		if(campusinfoFrontUser != null){
			//TODO 校验前端用户是否允许登陆
			// 校验前端用户是否允许登陆
			if (Global.NO.equals(campusinfoFrontUser.getLoginFlag())){
				throw new AuthenticationException("msg:该前端帐号禁止登录.");
			}
			byte[] salt_Front = Encodes.decodeHex(campusinfoFrontUser.getPassword().substring(0,16));
			return new SimpleAuthenticationInfo(new Principal(campusinfoFrontUser, token.isMobileLogin()),
					campusinfoFrontUser.getPassword().substring(16),ByteSource.Util.bytes(salt_Front),getName());
		}else{
			return null;
		}
	}
}


/**
 * 授权用户信息
 */
public static class Principal implements Serializable {

	private static final long serialVersionUID = 1L;
	
	private String id; // 编号
	private String loginName; // 登录名
	private String name; // 姓名
	private boolean mobileLogin; // 是否手机登录
	//private Map cacheMap;

	//TODO 前端用户授权构造器方法
	// 前端用户授权构造器
	public Principal(CampusinfoFrontUser campusinfoFrontUser, boolean mobileLogin){
		this.id = campusinfoFrontUser.getId();
		this.loginName = campusinfoFrontUser.getLoginName();
		this.name = campusinfoFrontUser.getName();
		this.mobileLogin = mobileLogin;
	}

	// 后台用户授权构造器
	public Principal(User user, boolean mobileLogin) {
		this.id = user.getId();
		this.loginName = user.getLoginName();
		this.name = user.getName();
		this.mobileLogin = mobileLogin;
	}

	public String getId() {
		return id;
	}

	public String getLoginName() {
		return loginName;
	}

	public String getName() {
		return name;
	}

	public boolean isMobileLogin() {
		return mobileLogin;
	}

    //@JsonIgnore
    //public Map getCacheMap() {
        //if (cacheMap==null){
        //cacheMap = new HashMap();
    //}
    //return cacheMap;
    //}

	/**
	 * 获取SESSIONID
	 */
	public String getSessionid() {
		try{
			return (String) UserUtils.getSession().getId();
		}catch (Exception e) {
			return "";
		}
	}
	
	@Override
	public String toString() {
		return id;
	}

}

FrontUserUtils:

/**
* Created by yt on 2017/10/26.
* 前端用户工具类
*/
public class FrontUserUtils {

//TODO 前端用户DAO
//前端用户DAO
private static CampusinfoFrontUserDao campusinfoFrontUserDao = SpringContextHolder.getBean(CampusinfoFrontUserDao.class);
private static RoleDao roleDao = SpringContextHolder.getBean(RoleDao.class);

public static final String FRONTUSER_CACHE = "frontuserCache";
public static final String USER_CACHE_ID_ = "id_";
public static final String USER_CACHE_LOGIN_NAME_ = "ln";

//TODO 根据前端登录名获取用户
/**
 * 根据前端登录名获取用户
 * @param loginName
 * @return 取不到返回null
 */

public static CampusinfoFrontUser getByFrontLoginName(String loginName){
    CampusinfoFrontUser campusinfoFrontUser = (CampusinfoFrontUser)CacheUtils.get(FRONTUSER_CACHE,USER_CACHE_LOGIN_NAME_ + loginName);
    if (campusinfoFrontUser == null) {
        campusinfoFrontUser = campusinfoFrontUserDao.getByFrontLoginName(new CampusinfoFrontUser(null, loginName));
        if (campusinfoFrontUser == null) {
            return null;
        }

        campusinfoFrontUser.setRoleList(roleDao.findList(new Role(campusinfoFrontUser)));
        CacheUtils.put(FRONTUSER_CACHE, USER_CACHE_ID_ + campusinfoFrontUser.getId(), campusinfoFrontUser);
        CacheUtils.put(FRONTUSER_CACHE, USER_CACHE_LOGIN_NAME_ + campusinfoFrontUser.getLoginName(), campusinfoFrontUser);
    }
    return campusinfoFrontUser;
}

//TODO 获取当前登录者对象
/**
 * 获取当前登录者对象
 */
public static Principal getPrincipal(){
    try{
        Subject subject = SecurityUtils.getSubject();
        Principal principal;
        principal = (Principal)subject.getPrincipal();
        if (principal != null){
            return principal;
        }
        //subject.logout();
    }catch (UnavailableSecurityManagerException e) {

    }catch (InvalidSessionException e){

    }
    return null;
}

CampusinfoFrontUserDao:

/**
 * 根据前端登录名称查询用户
 * @param loginName
 * @return CampusinfoFrontUser 对象
 */
public CampusinfoFrontUser getByFrontLoginName(CampusinfoFrontUser campusinfoFrontUser);

CampusinfoFrontUserDao.xml:


RoleDao.xml:

你可能感兴趣的:(java)