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>
---
沒有留言:
張貼留言