JOpenId 就是 OpenId 的一種實做,千萬別用 OpenId4J,太底層了。
這邊是用 Spring MVC 3.1.2 做 Webapp。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>idv.neil.springMVC312</groupId> <artifactId>springMVC312</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>springMVC312 Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.glassfish.hk2.external</groupId> <artifactId>javax.inject</artifactId> <version>2.1.13</version> </dependency> <dependency> <groupId>org.expressme</groupId> <artifactId>JOpenId</artifactId> <version>1.08</version> </dependency> </dependencies> <build> <finalName>springMVC312</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <!-- 更改 output 目錄 --> <buildOutputDirectory>src/main/webapp/WEB-INF/classes</buildOutputDirectory> <!-- 讓產生的 .classpath 包含 source jar --> <downloadSources>true</downloadSources> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.2</version> </plugin> </plugins> </build> </project>
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-app.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springMVC312</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-web.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC312</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
/WEB-INF/spring-web.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd "> <mvc:annotation-driven /> <context:component-scan base-package="idv.neil.web" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
/WEB-INF/views/jopenid/index.jsp
<?xml version="1.0" encoding="UTF-8" ?> <%@ page language="java" contentType="text/html; charset=BIG5" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Index</title> </head> <body> <a href="./login?op=Google">Login Google</a> <a href="./login?op=Yahoo">Login Yahoo</a> </body> </html>
JOpenIDCtrl.java
package idv.neil.web; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashSet; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.expressme.openid.Association; import org.expressme.openid.Authentication; import org.expressme.openid.Endpoint; import org.expressme.openid.OpenIdException; import org.expressme.openid.OpenIdManager; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * @author Neil Chan * @date 2012/8/2 */ @Controller @RequestMapping("/jopenid") public class JOpenIDCtrl { private static final String ATTR_MAC = "openid_mac"; private static final String ATTR_ALIAS = "openid_alias"; private static final long ONE_HOUR = 3600000L; private static final long TWO_HOUR = ONE_HOUR * 2L; // private static final long TWO_HOUR = 30 * 1000; /** simulate a database that store all nonce */ private Set<String> nonceDb = new HashSet<String>(); private OpenIdManager manager; private JOpenIDCtrl() { super(); this.manager = new OpenIdManager(); this.manager.setRealm("http://localhost/springMVC312/"); // 成功登入後回來的 url this.manager.setReturnTo("http://localhost/springMVC312/jopenid/home"); } @RequestMapping("/index") public String index(Model model) { return "jopenid/index"; } @RequestMapping("/login") public void login(@RequestParam("op") String op, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException { if (op.equals("Google") || op.equals("Yahoo")) { // 到 JOpenId-1.08.jar/openid-providers.properties 找出 provider url // 若要新增其他 provider url,可以在 WEB-INF/classes 下新增 openid-providers.properties 檔案 Endpoint endpoint = manager.lookupEndpoint(op); Association association = manager.lookupAssociation(endpoint); session.setAttribute(ATTR_MAC, association.getRawMacKey()); session.setAttribute(ATTR_ALIAS, endpoint.getAlias()); String url = manager.getAuthenticationUrl(endpoint, association); response.sendRedirect(url); } else { throw new RuntimeException("Unsupported OP: " + op); } } @RequestMapping("/home") public String home(@RequestParam("openid.response_nonce") String nonce, Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response) { // check sign on result from Google or Yahoo: this.checkNonce(nonce); // get authentication: byte[] mac_key = (byte[]) session.getAttribute(ATTR_MAC); String alias = (String) session.getAttribute(ATTR_ALIAS); Authentication authentication = manager.getAuthentication(request, mac_key, alias); model.addAttribute("identity", authentication.getIdentity()); model.addAttribute("email", authentication.getEmail()); model.addAttribute("fullname", authentication.getFullname()); model.addAttribute("firstname", authentication.getFirstname()); model.addAttribute("lastname", authentication.getLastname()); model.addAttribute("gender", authentication.getGender()); model.addAttribute("language", authentication.getLanguage()); return "jopenid/home"; } /** * 隨機數檢查 - 用來防止 Replay Attack,也就是駭客攔截該 response,並用來再次登入 * 所以要檢查時間不可以超過一小時,以及該隨機數已經使用過 * 隨機數長得像 2009-10-21T02:11:39Zrhco-EsNzi8FtQ 這樣 * 2009-10-21T02:11:39 是當下時間,Zrhco-EsNzi8FtQ 是隨機產生 * * @param nonce */ private void checkNonce(String nonce) { // check response_nonce to prevent replay-attack: if (nonce == null || nonce.length() < 20) { throw new OpenIdException("Verify failed."); } SimpleDateFormat dateFormat = this.createDateFormat(); // make sure the time of server is correct: long nonceTime = this.getNonceTime(nonce, dateFormat); long diff = Math.abs(System.currentTimeMillis() - nonceTime); if (diff > ONE_HOUR) { throw new OpenIdException("Bad nonce time."); } if (this.isNonceExist(nonce)) { throw new OpenIdException("Verify nonce failed."); } this.storeNonce(nonce, dateFormat); } private long getNonceTime(String nonce, SimpleDateFormat dateFormat) { try { return dateFormat.parse(nonce.substring(0, 19) + "+0000").getTime(); } catch (ParseException e) { throw new OpenIdException("Bad nonce time."); } } private SimpleDateFormat createDateFormat() { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); } /** * check if nonce is exist in database * * @param nonce * @return */ private boolean isNonceExist(String nonce) { return this.nonceDb.contains(nonce); } /** * store nonce in database * * @param nonce * @param expires */ private void storeNonce(String nonce, SimpleDateFormat dateFormat) { // 清掉舊資料 long target = System.currentTimeMillis() - TWO_HOUR; for (String s : this.nonceDb) { long stime = this.getNonceTime(s, dateFormat); if (stime < target) { System.out.println("Remove " + s); this.nonceDb.remove(s); } else { System.out.println("Keep " + s); } } this.nonceDb.add(nonce); } }
<?xml version="1.0" encoding="UTF-8" ?> <%@ page language="java" contentType="text/html; charset=BIG5" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Home</title> </head> <body> Back home~<br/> identity: <c:out value="${identity}"/><br/> email: <c:out value="${email}"/><br/> fullname: <c:out value="${fullname}"/><br/> firstname: <c:out value="${firstname}"/><br/> lastname: <c:out value="${lastname}"/><br/> gender: <c:out value="${gender}"/><br/> language: <c:out value="${language}"/><br/> </body> </html>
---
沒有留言:
張貼留言