JavaWeb簡單的單點登錄、驗證碼校驗功能實現

前言

最近項目剛剛告一段落,後期有時間會慢慢分解整理出來給大家分享。本文主要提供思路和核心代碼,建立在有一定後臺基礎讀者上。(相信沒有基礎的同學只要認真細讀也是可以理解的大笑

技術原理

1、單點登錄(Single Sign On),簡稱爲 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。
現實中舉個栗子:頤和園是北京著名的旅遊景點。在頤和園內部有許多獨立的景點,例如“蘇州街”、“佛香閣”和“德和園”,都可以在各個景點門口單獨買票。很多遊客需要遊玩所有的景點,這種買票方式很不方便,需要在每個景點門口排隊買票,錢包拿進拿出的,容易丟失,很不安全。於是絕大多數遊客選擇在大門口買一張通票(也叫套票),就可以玩遍所有的景點而不需要重新再買票。他們只需要在每個景點門 口出示一下剛纔買的套票就能夠被允許進入每個獨立的景點。
單點登錄也一樣,當用戶第一次訪問應用系統的時候,因爲還沒有登錄,會被引導到認證系統中進行登錄;根據用戶提供的登錄信息,認證系統進行身份效驗(eg:用戶名、密碼、驗證碼校驗),如果通過校驗,應該返回給用戶一個認證的憑據--ticket;用戶再訪問應用其他模塊就會將這個ticket帶上,作爲自己認證的憑據,應用系統接受到請求之後會把ticket送到認證系統進行效驗(頁面攔截器校驗),檢查ticket的合法性。如果通過效驗,用戶就可以在不用再次登錄的情況下訪問
筆者代碼實現機制:建立用戶表SYS_USER存放用戶名、密碼、用戶id等字段,用到的唯一認證憑據ticket指的是:用戶名(loginName)、用戶id(userId),用戶校驗登錄成功後,用session存儲憑據,當用戶切換界面時,通過攔截器LoginInterceptor校驗用戶是否帶有認證憑據,從而實現單點登錄。
2、驗證碼校驗:加載登錄首頁時,通過Get方式獲取後臺生成的校驗碼,同時後臺用session存儲驗證碼(爲後續檢驗做準備),當前臺檢測到用戶填寫完驗證碼時,觸發機制,通過Get方式傳參給後臺匹配實現檢驗機制。

邏輯代碼

1、單點登錄

控制層LoginCtroller.java(result_code爲0表示登錄校驗成功,session保存的就是用戶認證憑據)
	@RequestMapping(value ="login.do",method = {RequestMethod.POST})
    public @ResponseBody JSONObject login(@RequestBody JSONObject loginJson,HttpServletRequest request, HttpServletResponse response){
		
		// 登錄校驗
		JSONObject	resultJson = userService.login(loginJson);
		if (resultJson.getIntValue("result_code") == 0) {
			SysUser sysUser =(SysUser) resultJson.get("sysUser");
			// 創建登錄Session信息
			resultJson.put("id", sysUser.getId());
			resultJson.put("name", sysUser.getName());
			resultJson.put("loginName", sysUser.getLoginName());
			this.initSession(request, sysUser);
			logger.info(String.format("用戶:%s 登錄系統,登錄時間:%s", loginJson.getString("loginName")));
		} 
		return resultJson;
		
    }
	
	private void initSession(HttpServletRequest request,SysUser sysUser) {
		//創建登錄Session信息
		HttpSession httpSession = request.getSession();
		httpSession.setAttribute("loginName", sysUser.getLoginName());
		httpSession.setAttribute("userId", sysUser.getId());
	}
接口實現類UserServiceImpl.java(接口類UserService.java)中的登錄校驗方法,這裏面主要是獲取前臺傳遞的用戶信息參數,再通過用戶名查詢數據庫用戶信息,可能難點是用MD5密碼加密覈對信息進行校驗。
public JSONObject login(JSONObject jSONObject){
		
		JSONObject resultJson = new JSONObject();
		
		
		try {
			if(StringUtils.isBlank( jSONObject.getString("loginName"))){
				throw new RuntimeException("登錄用戶名不能爲空!");
			}
			if(StringUtils.isBlank( jSONObject.getString("password"))){
				throw new RuntimeException("登錄必須填寫密碼!");
			}

			String loginName = jSONObject.getString("loginName");
			SysUser sysUser = sysUserMapper.findByLoginName(loginName);
			if(sysUser==null){
				throw new RuntimeException("用戶不存在!");
			}
			if(StringUtils.isBlank(jSONObject.getString("password"))){
				throw new RuntimeException("登錄密碼不能爲空!");
			}
			String password = MD5Util.getMD5String(jSONObject.getString("password"));
			if(StringUtils.isBlank(sysUser.getPassword())  || !sysUser.getPassword().equals((password))){
				throw new RuntimeException("密碼錯誤!");
			}
			
			resultJson.put("result_code", 0);
			resultJson.put("result_detail", "success");
			resultJson.put("sysUser", sysUser);
		
		} catch (RuntimeException e){
			resultJson.put("result_code", -2);
			resultJson.put("result_detail", e.getMessage());
			logger.error("login ",e);
		}catch (Exception e){
			resultJson.put("result_code", -1);
			resultJson.put("result_detail", e.getMessage());
			logger.error("login ",e);
		}
		return resultJson;
}
加密工具類MD5Util.java
package com.kilomob.powernetwork.permission.common;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * @author fengjk
 *
 */
public class MD5Util {
	
	private static Log log = LogFactory.getLog(MD5Util.class);
	/**
	 * 默認的密碼字符串組合,apache校驗下載的文件的正確性用的就是默認的這個組合
	 */
	protected static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

	protected static MessageDigest messagedigest = null;
	static {
		try {
			messagedigest = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException nsaex) {
			log.error(MD5Util.class.getName() + "初始化失敗,MessageDigest不支持MD5Util。");
			nsaex.printStackTrace();
		}
	}
	
	public static String getMD5String(String str){
		if(str!=null){
			messagedigest.update(str.getBytes());
			return bufferToHex(messagedigest.digest());
		}else{
			return null;
		}
	}

	private static String bufferToHex(byte bytes[]) {
		return bufferToHex(bytes, 0, bytes.length);
	}

	private static String bufferToHex(byte bytes[], int m, int n) {
		StringBuffer stringbuffer = new StringBuffer(2 * n);
		int k = m + n;
		for (int l = m; l < k; l++) {
			appendHexPair(bytes[l], stringbuffer);
		}
		return stringbuffer.toString();
	}

	private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
		char c0 = hexDigits[(bt & 0xf0) >> 4];
		char c1 = hexDigits[bt & 0xf];
		stringbuffer.append(c0);
		stringbuffer.append(c1);
	}

}
攔截器配置applicationContext.xml
	<!-- 類的存放路徑class
    	ignoreUrlList和interceptro指的是忽略,不攔截-->
	<bean id="loginInterceptor" class="com.kilomob.powernetwork.managerweb.interceptor.LoginInterceptor">
		<property name="loginPage" value="/login.html"></property>
		<property name="ignoreUrlList">
			<list>
				<value>/api/login.do</value>
				<value>/login.html</value>
				<value>/api/loginout.do</value>
				<value>/api/loginValidate.do</value>
				<value>/api/imgcode</value>
				<value>/api/vali/imagecode</value>
			</list>
		</property>
	</bean>
	
	<mvc:interceptors>
        <mvc:interceptor>
        	 <mvc:mapping path="/**"/>
        	 <mvc:exclude-mapping path="/api/imgcode"/>
        	 <mvc:exclude-mapping path="/api/vali/imagecode"/>
        	 <ref bean="loginInterceptor"/>
        </mvc:interceptor>
     </mvc:interceptors>
注意此處的loginPage和ignoreUrlList應與下面的攔截類變量名一致。
攔截類LoginInterceptor.java
package com.kilomob.powernetwork.managerweb.interceptor;

import java.io.File;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.kilomob.powernetwork.managerweb.util.WebConfig;

/**
 * @Description:登錄攔截器
 * @author: fengjk
 * @time:2017年3月20日 下午8:11:25
 */
public class LoginInterceptor implements HandlerInterceptor {
	
	private String loginPage;
	private List<String> ignoreUrlList;

	public String getLoginPage() {
		return loginPage;
	}

	public void setLoginPage(String loginPage) {
		this.loginPage = loginPage;
	}

	public List<String> getIgnoreUrlList() {
		return ignoreUrlList;
	}

	public void setIgnoreUrlList(List<String> ignoreUrlList) {
		this.ignoreUrlList = ignoreUrlList;
	}

	@Override
	public boolean preHandle(HttpServletRequest paramHttpServletRequest,HttpServletResponse paramHttpServletResponse, Object paramObject)throws Exception {
		paramHttpServletResponse.addHeader("P3P", "CP=CAO PSA OUR");
		String path = paramHttpServletRequest.getRequestURI();
		boolean ignore = false;
		for (String url : ignoreUrlList) {
			if (path.contains(url)) {
				ignore = true;
				break;
			}
		}
		if (ignore) {
			return true;
		}

		HttpSession httpSession = paramHttpServletRequest.getSession();
		if (httpSession.getAttribute("userId") == null && httpSession.getAttribute("loginName") == null) {
			paramHttpServletResponse.setContentType("text/html;charset=UTF-8");
			paramHttpServletResponse.sendRedirect("http://127.0.0.1:8080/managerweb/login.html");
			return false;
		}
		paramHttpServletRequest.setAttribute("loginName", httpSession.getAttribute("loginName"));
		paramHttpServletRequest.setAttribute("userId", httpSession.getAttribute("userId"));
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest paramHttpServletRequest,HttpServletResponse paramHttpServletResponse, Object paramObject,ModelAndView paramModelAndView) throws Exception {
		
	}

	@Override
	public void afterCompletion(HttpServletRequest paramHttpServletRequest,HttpServletResponse paramHttpServletResponse, Object paramObject,Exception paramException) throws Exception {
	}

}
通過session檢驗用戶是否已經登錄過,否的話則跳轉回首頁。關於CAS實現的單點登錄可參考:http://blog.csdn.net/small_love/article/details/6664831/

2、驗證碼校驗

控制層LoginCtroller.java
	@RequestMapping(value ="/imgcode",method = {RequestMethod.GET})
	public void getImgCode(HttpServletRequest request,HttpServletResponse response) throws IOException {
		HttpSession session = request.getSession();
		session.removeAttribute("code");
		response.setContentType("image/jpeg");
		ServletOutputStream sos = response.getOutputStream();

		response.setHeader("Pragma", "No-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);

		BufferedImage image = new BufferedImage(WIDTH, HEIGHT,BufferedImage.TYPE_INT_RGB);
		Graphics g = image.getGraphics();

		char[] rands = generateCheckCode();
		drawBackground(g);
		drawRands(g, rands);
		g.dispose();

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ImageIO.write(image, "JPEG", bos);
		byte[] buf = bos.toByteArray();
		response.setContentLength(buf.length);
		sos.write(buf);
		bos.close();
		sos.close();
		session.setAttribute("code", new String(rands));
	}

	private void drawBackground(Graphics g) {
		g.setColor(new Color(72, 75, 83));
		g.fillRect(0, 0, WIDTH, HEIGHT);
		/*for (int i = 0; i < 120; i++) {
			int x = (int) (Math.random() * WIDTH);
			int y = (int) (Math.random() * HEIGHT);
			int red = (int) (Math.random() * 255);
			int green = (int) (Math.random() * 255);
			int blue = (int) (Math.random() * 255);
			g.setColor(new Color(red, green, blue));
			g.drawOval(x, y, 1, 0);
		}*/
	}

	private void drawRands(Graphics g, char[] rands) {
		g.setColor(new Color(0xe0e0e0));
		g.setFont(new Font("Arial", Font.BOLD | Font.ITALIC, 24));
		g.drawString("" + rands[0], 1, 27);
		g.drawString("" + rands[1], 19, 25);
		g.drawString("" + rands[2], 39, 27);
		g.drawString("" + rands[3], 58, 26);
	}

	private char[] generateCheckCode() {
		String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		char[] rands = new char[4];
		for (int i = 0; i < 4; i++) {
			int rand = (int) (Math.random() * 62);
			rands[i] = chars.charAt(rand);
		}
		return rands;
	}
	
	/**
	 * @Description:校驗驗證碼
	 * @param imagecode
	 * @param request
	 * @param response
	 * @return
	 * boolean
	 * @exception:
	 * @author: fengjk
	 * @time:2017年3月27日 下午3:45:12
	 */
    @RequestMapping(value= "/vali/imagecode/{imagecode}" ,method = {RequestMethod.GET} )
    public int valideImage(@PathVariable(name = "imagecode") String imagecode,HttpServletRequest request,HttpServletResponse response) {
    	HttpSession session = request.getSession();
		String code = (String)session.getAttribute("code");
		if(code != null && code.toUpperCase().equals(imagecode.toUpperCase())){
			return 0;
		}
    	return 1;
    	
    }
前臺加載首頁時通過Get方式請求getImgCode方法獲取驗證碼,後臺同時用session保存數據,當校驗驗證碼時,通過valideImage方法校驗,返回0說明校驗成功。

總結

篇幅不長,相信讀者在理解實現原理基礎上回歸代碼會比較通俗易懂。文章如有誤處和不足,請及時留言告知筆者,萬分感謝!歡迎加羣互相探討學習,qq:583138104


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章