jFinal中有oauth2 网页oauth接口权限认证模块吗

33956人阅读
文章作者: 本文出自 ,禁止用于商业用途,转载请注明出处。&& 原文链接:
消息体加密随着微信服务开发在越来越多的领域应用,应用的安全性逐渐被重视起来。本文主要阐述如何为微信的消息加密的原理与Java版本的实现。原理做过微信开发的朋友们都知道,微信使用xml格式的消息结构。这里着重说一下最核心的收发消息。在明文模式中,POST请求有signature, timestamp, nonce三个参数。它的Body为一个XML:&xml&
&ToUserName&&![CDATA[toUser]]&&/ToUserName&
&FromUserName&&![CDATA[fromUser]]&&/FromUserName&
&CreateTime&&/CreateTime&
&MsgType&&![CDATA[text]]&&/MsgType&
&Content&&![CDATA[this is a test]]&&/Content&
&MsgId&3456&/MsgId&
&/xml&而在加密模式中,增加了两个参数:encrypt_type 和 msg_signature 。它的Body 也变更为:&xml&
&ToUserName&&![CDATA[toUser]]&&/ToUserName&
&Encrypt&&![CDATA[encryptData]]&&/Encrypt&
&/xml&其中encryptData是加密后的信息,通过我们在微信开发平台配置的EncodingAESKey, Token, 以及自动生成的AppID,通过加密算法,可以验证信息的真实性,并且将真实的信息,解析成如明文的格式的XML。整条信息是加密的,因此不用担心被伪造,否则,会有风险。PS:会有怎样的风险?如果是Token被人猜出来进而伪造内容,加密模式中的EncodingAESKey不会么?还是担心消息类型会泄露?没太想明白,希望路过的大神指点 不管怎么说,加密看起来更安全些,我现在做一个金融类的项目,有备无患,就使用了加密的功能。代码实现微信提供了一个各种语言的解析,我在上面稍微封装了一下:public class SignUtil {
与开发模式接口配置信息中的Token保持一致
private static String token = &yourToken&;
* 微信生成的 ASEKey
private static String encodingAesKey =&yourKey&;
* 应用的AppId
private static String appId=&yourId&;
* 解密微信发过来的密文
* @return 加密后的内容
public static String decryptMsg(String msgSignature,String timeStamp,String nonce,String encrypt_msg) {
String result =&&;
pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
result = pc.decryptMsg(msgSignature, timeStamp, nonce, encrypt_msg);
} catch (AesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
* 加密给微信的消息内容
* @param replayMsg
* @param timeStamp
* @param nonce
public static String ecryptMsg(String replayMsg,String timeStamp, String nonce) {
String result =&&;
pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
result = pc.encryptMsg(replayMsg, timeStamp, nonce);
} catch (AesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}最后,在对应POST的处理方法中调用://request 为请求的 HttpServletRequest 参数
String encrypt_type =request.getParameter(&encrypt_type&);
if (encrypt_type == null || encrypt_type.equals(&raw&)) { //不用加密
// 微信加密签名
String signature = request.getParameter(&signature&);
String timestamp = request.getParameter(&timestamp&);
String nonce = request.getParameter(&nonce&);
if(SignUtil.checkSignature(signature, timestamp, nonce)) {
//业务处理方法,返回为XML格式的结果信息 此处需要转换一下编码,否则中文乱码,不知为何...
String respMessage = new String(wxService.processRequest(request.getInputStream(),session).getBytes(&UTF-8&),&ISO8859_1&);
(&message:&+respMessage);
return respM
return &check Error&;
} else {//需走加解密流程
// 微信加密签名
String msgSignature = request.getParameter(&msg_signature&);
String timeStamp = request.getParameter(&timestamp&);
String nonce = request.getParameter(&nonce&);
String encryptMsg = readLine(request.getInputStream());
String data = SignUtil.decryptMsg(msgSignature, timeStamp, nonce, encryptMsg);
(&parse Data is:&+data);
if(data.length() == 0) {
return &CheckError&;
InputStream istream = new ByteArrayInputStream(data.getBytes());
//业务处理方法,返回为XML格式的结果信息
String respMessage = wxService.processRequest(istream,session);
(&message:&+respMessage);
String enRespMsg = SignUtil.ecryptMsg(respMessage, timeStamp, nonce);
return enRespM
}使用上面的方法,可以同时处理明文模式与加密模式,从真正意义上做到闲的蛋疼 如果你觉得这篇文章对你有帮助,可以顺手点个顶,不但不会喜当爹,还能让更多人能看到它...&
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2052039次
积分:17100
积分:17100
排名:第604名
原创:218篇
转载:19篇
评论:679条
左手代码右手艺术 追求新浪潮。
个人网站:
: Unity3D内购插件
: Unity3D常用功能合集
微信公众号:松阳论道
我写的书:
文章:10篇
阅读:12774
文章:20篇
阅读:83122
阅读:161625
文章:18篇
阅读:595844
文章:15篇
阅读:130557
(1)(1)(2)(5)(10)(1)(1)(1)(2)(1)(3)(1)(3)(1)(3)(1)(4)(1)(5)(5)(6)(4)(1)(1)(3)(1)(1)(1)(2)(6)(8)(1)(4)(2)(7)(10)(7)(3)(3)(1)(3)(1)(4)(11)(15)(21)(27)(29)(1)(3)博客分类:
目前很多开放平台如新浪微博开放平台都在使用提供开放API接口供开发者使用,随之带来了第三方应用要到开放平台进行授权的问题,OAuth就是干这个的,OAuth2是OAuth协议的下一个版本,相比OAuth1,OAuth2整个授权流程更简单安全了,但不兼容OAuth1,具体可以到OAuth2官网查看,OAuth2协议规范可以参考。目前有好多参考实现供选择,可以到其官网查看下载。
本文使用,其之前的名字叫Apache Amber ,是Java版的参考实现。使用文档可参考。
资源拥有者(resource owner):能授权访问受保护资源的一个实体,可以是一个人,那我们称之为最终用户;如新浪微博用户zhangsan;
资源服务器(resource server):存储受保护资源,客户端通过access token请求资源,资源服务器响应受保护资源给客户端;存储着用户zhangsan的微博等信息。
授权服务器(authorization server):成功验证资源拥有者并获取授权之后,授权服务器颁发授权令牌(Access Token)给客户端。
客户端(client):如新浪微博客户端weico、微格等第三方应用,也可以是它自己的官方应用;其本身不存储资源,而是资源拥有者授权通过后,使用它的授权(授权令牌)访问受保护资源,然后客户端把相应的数据展示出来/提交到服务器。“客户端”术语不代表任何特定实现(如应用运行在一台服务器、桌面、手机或其他设备)。
OAuth2协议流程
1、客户端从资源拥有者那请求授权。授权请求可以直接发给资源拥有者,或间接的通过授权服务器这种中介,后者更可取。
2、客户端收到一个授权许可,代表资源服务器提供的授权。
3、客户端使用它自己的私有证书及授权许可到授权服务器验证。
4、如果验证成功,则下发一个访问令牌。
5、客户端使用访问令牌向资源服务器请求受保护资源。
6、资源服务器会验证访问令牌的有效性,如果成功则下发受保护资源。
更多流程的解释请参考OAuth2的协议规范。
本文把授权服务器和资源服务器整合在一起实现。
此处我们使用apache oltu oauth2服务端实现,需要引入authzserver(授权服务器依赖)和resourceserver(资源服务器依赖)。
&dependency&
&groupId&org.apache.oltu.oauth2&/groupId&
&artifactId&org.apache.oltu.oauth2.authzserver&/artifactId&
&version&0.31&/version&
&/dependency&
&dependency&
&groupId&org.apache.oltu.oauth2&/groupId&
&artifactId&org.apache.oltu.oauth2.resourceserver&/artifactId&
&version&0.31&/version&
&/dependency&
其他的请参考pom.xml。
用户(oauth2_user)
客户端(oauth2_client)
client_name
客户端名称
client_secret
客户端安全key
用户表存储着认证/资源服务器的用户信息,即资源拥有者;比如用户名/密码;客户端表存储客户端的的客户端id及客户端安全key;在进行授权时使用。
表及数据SQL
具体请参考
sql/ shiro-schema.sql (表结构)
sql/ shiro-data.sql
(初始数据)
默认用户名/密码是admin/123456。
具体请参考com.github.zhangkaitao.shiro.chapter17.entity包下的实体,此处就不列举了。
具体请参考com.github.zhangkaitao.shiro.chapter17.dao包下的DAO接口及实现。
具体请参考com.github.zhangkaitao.shiro.chapter17.service包下的Service接口及实现。以下是出了基本CRUD之外的关键接口:
public interface UserService {
public User createUser(User user);// 创建用户
public User updateUser(User user);// 更新用户
public void deleteUser(Long userId);// 删除用户
public void changePassword(Long userId, String newPassword); //修改密码
User findOne(Long userId);// 根据id查找用户
List&User& findAll();// 得到所有用户
public User findByUsername(String username);// 根据用户名查找用户
public interface ClientService {
public Client createClient(Client client);// 创建客户端
public Client updateClient(Client client);// 更新客户端
public void deleteClient(Long clientId);// 删除客户端
Client findOne(Long clientId);// 根据id查找客户端
List&Client& findAll();// 查找所有
Client findByClientId(String clientId);// 根据客户端id查找客户端
Client findByClientSecret(String clientSecret);//根据客户端安全KEY查找客户端
public interface OAuthService {
public void addAuthCode(String authCode, String username);// 添加 auth code
public void addAccessToken(String accessToken, String username); // 添加 access token
boolean checkAuthCode(String authCode); // 验证auth code是否有效
boolean checkAccessToken(String accessToken); // 验证access token是否有效
String getUsernameByAuthCode(String authCode);// 根据auth code获取用户名
String getUsernameByAccessToken(String accessToken);// 根据access token获取用户名
long getExpireIn();//auth code / access token 过期时间
public boolean checkClientId(String clientId);// 检查客户端id是否存在
public boolean checkClientSecret(String clientSecret);// 坚持客户端安全KEY是否存在
此处通过OAuthService实现进行auth code和access token的维护。
后端数据维护控制器
具体请参考com.github.zhangkaitao.shiro.chapter17.web.controller包下的IndexController、LoginController、UserController和ClientController,其用于维护后端的数据,如用户及客户端数据;即相当于后台管理。
授权控制器AuthorizeController
@Controller
public class AuthorizeController {
@Autowired
private OAuthService oAuthS
@Autowired
private ClientService clientS
@RequestMapping("/authorize")
public Object authorize(Model model,
HttpServletRequest request)
throws URISyntaxException, OAuthSystemException {
//构建OAuth 授权请求
OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);
//检查传入的客户端id是否正确
if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
Subject subject = SecurityUtils.getSubject();
//如果用户没有登录,跳转到登陆页面
if(!subject.isAuthenticated()) {
if(!login(subject, request)) {//登录失败时跳转到登陆页面
model.addAttribute("client",
clientService.findByClientId(oauthRequest.getClientId()));
return "oauth2login";
String username = (String)subject.getPrincipal();
//生成授权码
String authorizationCode =
//responseType目前仅支持CODE,另外还有TOKEN
String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
if (responseType.equals(ResponseType.CODE.toString())) {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
authorizationCode = oauthIssuerImpl.authorizationCode();
oAuthService.addAuthCode(authorizationCode, username);
//进行OAuth响应构建
OAuthASResponse.OAuthAuthorizationResponseBuilder builder =
OAuthASResponse.authorizationResponse(request,
HttpServletResponse.SC_FOUND);
//设置授权码
builder.setCode(authorizationCode);
//得到到客户端重定向地址
String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
//构建响应
final OAuthResponse response = builder.location(redirectURI).buildQueryMessage();
//根据OAuthResponse返回ResponseEntity响应
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
} catch (OAuthProblemException e) {
//出错处理
String redirectUri = e.getRedirectUri();
if (OAuthUtils.isEmpty(redirectUri)) {
//告诉客户端没有传入redirectUri直接报错
return new ResponseEntity(
"OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND);
//返回错误消息(如?error=)
final OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.error(e).location(redirectUri).buildQueryMessage();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
private boolean login(Subject subject, HttpServletRequest request) {
if("get".equalsIgnoreCase(request.getMethod())) {
String username = request.getParameter("username");
String password = request.getParameter("password");
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
} catch (Exception e) {
request.setAttribute("error", "登录失败:" + e.getClass().getName());
如上代码的作用:
1、首先通过如http://localhost:8080/chapter17-server/authorize
?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login访问授权页面;
2、该控制器首先检查clientId是否正确;如果错误将返回相应的错误信息;
3、然后判断用户是否登录了,如果没有登录首先到登录页面登录;
4、登录成功后生成相应的auth code即授权码,然后重定向到客户端地址,如http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dffae995da0ed;在重定向到的地址中会带上code参数(授权码),接着客户端可以根据授权码去换取access token。
访问令牌控制器AccessTokenController
@RestController
public class AccessTokenController {
@Autowired
private OAuthService oAuthS
@Autowired
private UserService userS
@RequestMapping("/accessToken")
public HttpEntity token(HttpServletRequest request)
throws URISyntaxException, OAuthSystemException {
//构建OAuth请求
OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);
//检查提交的客户端id是否正确
if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
// 检查客户端安全KEY是否正确
if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);
// 检查验证类型,此处只检查AUTHORIZATION_CODE类型,其他的还有PASSWORD或REFRESH_TOKEN
if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(
GrantType.AUTHORIZATION_CODE.toString())) {
if (!oAuthService.checkAuthCode(authCode)) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_GRANT)
.setErrorDescription("错误的授权码")
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
//生成Access Token
OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
final String accessToken = oauthIssuerImpl.accessToken();
oAuthService.addAccessToken(accessToken,
oAuthService.getUsernameByAuthCode(authCode));
//生成OAuth响应
OAuthResponse response = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setExpiresIn(String.valueOf(oAuthService.getExpireIn()))
.buildJSONMessage();
//根据OAuthResponse生成ResponseEntity
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
} catch (OAuthProblemException e) {
//构建错误响应
OAuthResponse res = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
.buildJSONMessage();
return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));
如上代码的作用:
1、首先通过如http://localhost:8080/chapter17-server/accessToken,POST提交如下数据:client_id= c1ebe466-1cdc-4bd3-ab69-77c3561b9dee& client_secret= d7-43ed-ad68-19c0f971738b&grant_type=authorization_code&code=828beda584f37bcfd597b6&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login访问;
2、该控制器会验证client_id、client_secret、auth code的正确性,如果错误会返回相应的错误;
3、如果验证通过会生成并返回相应的访问令牌access token。
资源控制器UserInfoController
@RestController
public class UserInfoController {
@Autowired
private OAuthService oAuthS
@RequestMapping("/userInfo")
public HttpEntity userInfo(HttpServletRequest request) throws OAuthSystemException {
//构建OAuth资源请求
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);
//获取Access Token
String accessToken = oauthRequest.getAccessToken();
//验证Access Token
if (!oAuthService.checkAccessToken(accessToken)) {
// 如果不存在/过期了,返回未验证错误,需重新验证
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.setError(OAuthError.ResourceResponse.INVALID_TOKEN)
.buildHeaderMessage();
HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE,
oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);
//返回用户名
String username = oAuthService.getUsernameByAccessToken(accessToken);
return new ResponseEntity(username, HttpStatus.OK);
} catch (OAuthProblemException e) {
//检查是否设置了错误码
String errorCode = e.getError();
if (OAuthUtils.isEmpty(errorCode)) {
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.buildHeaderMessage();
HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE,
oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.setError(e.getError())
.setErrorDescription(e.getDescription())
.setErrorUri(e.getUri())
.buildHeaderMessage();
HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, 、
oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(HttpStatus.BAD_REQUEST);
如上代码的作用:
1、首先通过如http://localhost:8080/chapter17-server/userInfo? access_token=828beda584f37bcfd597b6进行访问;
2、该控制器会验证access token的有效性;如果无效了将返回相应的错误,客户端再重新进行授权;
3、如果有效,则返回当前登录用户的用户名。
Spring配置文件
具体请参考resources/spring*.xml,此处只列举spring-config-shiro.xml中的shiroFilter的filterChainDefinitions属性:
&property name="filterChainDefinitions"&
/login = authc
/logout = logout
/authorize=anon
/accessToken=anon
/userInfo=anon
/** = user
&/property&
对于oauth2的几个地址/authorize、/accessToken、/userInfo都是匿名可访问的。
其他源码请直接下载文档查看。
服务器维护
访问localhost:8080/chapter17-server/,登录后进行客户端管理和用户管理。
客户端管理就是进行客户端的注册,如新浪微博的第三方应用就需要到新浪微博开发平台进行注册;用户管理就是进行如新浪微博用户的管理。
对于授权服务和资源服务的实现可以参考新浪微博开发平台的实现:
客户端流程:如果需要登录首先跳到oauth2服务端进行登录授权,成功后服务端返回auth code,然后客户端使用auth code去服务器端换取access token,最好根据access token获取用户信息进行客户端的登录绑定。这个可以参照如很多网站的新浪微博登录功能,或其他的第三方帐号登录功能。
此处我们使用apache oltu oauth2客户端实现。
&dependency&
&groupId&org.apache.oltu.oauth2&/groupId&
&artifactId&org.apache.oltu.oauth2.client&/artifactId&
&version&0.31&/version&
&/dependency&
其他的请参考pom.xml。
OAuth2Token
类似于UsernamePasswordToken和CasToken;用于存储oauth2服务端返回的auth code。
public class OAuth2Token implements AuthenticationToken {
private String authC
public OAuth2Token(String authCode) {
this.authCode = authC
//省略getter/setter
OAuth2AuthenticationFilter
该filter的作用类似于FormAuthenticationFilter用于oauth2客户端的身份验证控制;如果当前用户还没有身份验证,首先会判断url中是否有code(服务端返回的auth code),如果没有则重定向到服务端进行登录并授权,然后返回auth code;接着OAuth2AuthenticationFilter会用auth code创建OAuth2Token,然后提交给Subject.login进行登录;接着OAuth2Realm会根据OAuth2Token进行相应的登录逻辑。
public class OAuth2AuthenticationFilter extends AuthenticatingFilter {
//oauth2 authc code参数名
private String authcCodeParam = "code";
//客户端id
private String clientId;
//服务器端登录成功/失败后重定向到的客户端地址
private String redirectU
//oauth2服务器响应类型
private String responseType = "code";
private String failureU
//省略setter
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest)
String code = httpRequest.getParameter(authcCodeParam);
return new OAuth2Token(code);
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String error = request.getParameter("error");
String errorDescription = request.getParameter("error_description");
if(!StringUtils.isEmpty(error)) {//如果服务端返回了错误
WebUtils.issueRedirect(request, response, failureUrl + "?error=" + error + "error_description=" + errorDescription);
Subject subject = getSubject(request, response);
if(!subject.isAuthenticated()) {
if(StringUtils.isEmpty(request.getParameter(authcCodeParam))) {
//如果用户没有身份验证,且没有auth code,则重定向到服务端授权
saveRequestAndRedirectToLogin(request, response);
//执行父类里的登录逻辑,调用Subject.login登录
return executeLogin(request, response);
//登录成功后的回调方法 重定向到成功页面
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
ServletResponse response) throws Exception {
issueSuccessRedirect(request, response);
//登录失败后的回调
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
ServletResponse response) {
Subject subject = getSubject(request, response);
if (subject.isAuthenticated() || subject.isRemembered()) {
try { //如果身份验证成功了 则也重定向到成功页面
issueSuccessRedirect(request, response);
} catch (Exception e) {
e.printStackTrace();
try { //登录失败时重定向到失败页面
WebUtils.issueRedirect(request, response, failureUrl);
} catch (IOException e) {
e.printStackTrace();
该拦截器的作用:
1、首先判断有没有服务端返回的error参数,如果有则直接重定向到失败页面;
2、接着如果用户还没有身份验证,判断是否有auth code参数(即是不是服务端授权之后返回的),如果没有则重定向到服务端进行授权;
3、否则调用executeLogin进行登录,通过auth code创建OAuth2Token提交给Subject进行登录;
4、登录成功将回调onLoginSuccess方法重定向到成功页面;
5、登录失败则回调onLoginFailure重定向到失败页面。
OAuth2Realm
public class OAuth2Realm extends AuthorizingRealm {
private String clientId;
private String clientS
private String accessTokenU
private String userInfoU
private String redirectU
//省略setter
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2T //表示此Realm只支持OAuth2Token类型
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationI
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
OAuth2Token oAuth2Token = (OAuth2Token)
String code = oAuth2Token.getAuthCode(); //获取 auth code
String username = extractUsername(code); // 提取用户名
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(username, code, getName());
return authenticationI
private String extractUsername(String code) {
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthClientRequest accessTokenRequest = OAuthClientRequest
.tokenLocation(accessTokenUrl)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId(clientId).setClientSecret(clientSecret)
.setCode(code).setRedirectURI(redirectUrl)
.buildQueryMessage();
//获取access token
OAuthAccessTokenResponse oAuthResponse =
oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);
String accessToken = oAuthResponse.getAccessToken();
Long expiresIn = oAuthResponse.getExpiresIn();
//获取user info
OAuthClientRequest userInfoRequest =
new OAuthBearerClientRequest(userInfoUrl)
.setAccessToken(accessToken).buildQueryMessage();
OAuthResourceResponse resourceResponse = oAuthClient.resource(
userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);
String username = resourceResponse.getBody();
} catch (Exception e) {
throw new OAuth2AuthenticationException(e);
此Realm首先只支持OAuth2Token类型的Token;然后通过传入的auth code去换取access token;再根据access token去获取用户信息(用户名),然后根据此信息创建AuthenticationInfo;如果需要AuthorizationInfo信息,可以根据此处获取的用户名再根据自己的业务规则去获取。
Spring shiro配置(spring-config-shiro.xml)
&bean id="oAuth2Realm"
class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2Realm"&
&property name="cachingEnabled" value="true"/&
&property name="authenticationCachingEnabled" value="true"/&
&property name="authenticationCacheName" value="authenticationCache"/&
&property name="authorizationCachingEnabled" value="true"/&
&property name="authorizationCacheName" value="authorizationCache"/&
&property name="clientId" value="c1ebe466-1cdc-4bd3-ab69-77c3561b9dee"/&
&property name="clientSecret" value="d7-43ed-ad68-19c0f971738b"/&
&property name="accessTokenUrl"
value="http://localhost:8080/chapter17-server/accessToken"/&
&property name="userInfoUrl" value="http://localhost:8080/chapter17-server/userInfo"/&
&property name="redirectUrl" value="http://localhost:9080/chapter17-client/oauth2-login"/&
此OAuth2Realm需要配置在服务端申请的clientId和clientSecret;及用于根据auth code换取access token的accessTokenUrl地址;及用于根据access token换取用户信息(受保护资源)的userInfoUrl地址。
&bean id="oAuth2AuthenticationFilter"
class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2AuthenticationFilter"&
&property name="authcCodeParam" value="code"/&
&property name="failureUrl" value="/oauth2Failure.jsp"/&
此OAuth2AuthenticationFilter用于拦截服务端重定向回来的auth code。
&bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"&
&property name="securityManager" ref="securityManager"/&
&property name="loginUrl" value="http://localhost:8080/chapter17-server/authorize?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login"/&
&property name="successUrl" value="/"/&
&property name="filters"&
&util:map&
&entry key="oauth2Authc" value-ref="oAuth2AuthenticationFilter"/&
&/util:map&
&/property&
&property name="filterChainDefinitions"&
/oauth2Failure.jsp = anon
/oauth2-login = oauth2Authc
/logout = logout
/** = user
&/property&
此处设置loginUrl为http://localhost:8080/chapter17-server/authorize
?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login";其会自动设置到所有的AccessControlFilter,如oAuth2AuthenticationFilter;另外/oauth2-login = oauth2Authc表示/oauth2-login地址使用oauth2Authc拦截器拦截并进行oauth2客户端授权。
1、首先访问,然后点击登录按钮进行登录,会跳到如下页面:
2、输入用户名进行登录并授权;
3、如果登录成功,服务端会重定向到客户端,即之前客户端提供的地址http://localhost:9080/chapter17-client/oauth2-login?code=473d56015bcf576f2ca03eac1a5bcc11,并带着auth code过去;
4、客户端的OAuth2AuthenticationFilter会收集此auth code,并创建OAuth2Token提交给Subject进行客户端登录;
5、客户端的Subject会委托给OAuth2Realm进行身份验证;此时OAuth2Realm会根据auth code换取access token,再根据access token获取受保护的用户信息;然后进行客户端登录。
到此OAuth2的集成就完成了,此处的服务端和客户端相对比较简单,没有进行一些异常检测,请参考如新浪微博进行相应API及异常错误码的设计。
示例源代码:;可加群
探讨Spring/Shiro技术。
浏览 117573
zqb666kkk 写道zqb666kkk 写道xiaozhi7616 写道worldfather168 写道worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}} 我也遇到 OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}这个错误了 请问怎么解决啊 博主!我调试发现 extractUsername这个方法进了两次 第一次的时候 .setCode(code)里的code不为null 第二次进来的时候 code 就为null了 这是怎么回事其实是在第一次登陆的时候保存了名为 "shiroSavedRequest"的session,该会话的successUrl是oauth2-login,所以在第一次调用onLoginSuccess的时候,会继续访问oauth2-login。解决方法是在第一次登陆之后,加上WebUtils.getAndClearSavedRequest(request);清楚shiroSavedRequest会话。就ok了兄弟你部起来可以用吗,我搭了博主提供的demo,跳转都不正常,你能提供个可用的的吗
zqb666kkk 写道xiaozhi7616 写道worldfather168 写道worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}} 我也遇到 OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}这个错误了 请问怎么解决啊 博主!我调试发现 extractUsername这个方法进了两次 第一次的时候 .setCode(code)里的code不为null 第二次进来的时候 code 就为null了 这是怎么回事其实是在第一次登陆的时候保存了名为 "shiroSavedRequest"的session,该会话的successUrl是oauth2-login,所以在第一次调用onLoginSuccess的时候,会继续访问oauth2-login。解决方法是在第一次登陆之后,加上WebUtils.getAndClearSavedRequest(request);清楚shiroSavedRequest会话。就ok了
zqb666kkk 写道OAuthProblemException{error='unsupported_response_type', description='Invalid response! Response body is not application/json encoded', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}请问这个错误怎么解决请问你解决了吗我搞不定,不知道什么问题
OAuthProblemException{error='unsupported_response_type', description='Invalid response! Response body is not application/json encoded', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}请问这个错误怎么解决请问你解决了吗
level one引用level two
zqb666kkk 写道xiaozhi7616 写道worldfather168 写道worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}} 我也遇到 OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}这个错误了 请问怎么解决啊 博主!我调试发现 extractUsername这个方法进了两次 第一次的时候 .setCode(code)里的code不为null 第二次进来的时候 code 就为null了 这是怎么回事是bug
zqb666kkk 写道xiaozhi7616 写道worldfather168 写道worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}} 我也遇到 OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}这个错误了 请问怎么解决啊 博主!我调试发现 extractUsername这个方法进了两次 第一次的时候 .setCode(code)里的code不为null 第二次进来的时候 code 就为null了 这是怎么回事我自己前面加了&
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
if(code!=null){
session.setAttribute("code", code);
code=(String) session.getAttribute("code");
String username = extractUsername(code);这个解决这个错误了 开涛兄 留了这个坑也不说明下
xiaozhi7616 写道worldfather168 写道worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}} 我也遇到 OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}这个错误了 请问怎么解决啊 博主!我调试发现 extractUsername这个方法进了两次 第一次的时候 .setCode(code)里的code不为null 第二次进来的时候 code 就为null了 这是怎么回事
worldfather168 写道worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}} 我也遇到 OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}这个错误了 请问怎么解决啊 博主!
worldfather168 写道worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。Server和client分别部署在两个Tomcat下也同样会报错,OAuthProblemException{error='invalid_request', description='Missing parameters: code', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}
worldfather168 写道michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest这是把服务端客户端在一个web环境才会这样,好像。
michnus 写道例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。onAccessDenied的时候saveRequestAndRedirectToLogin,onLoginSuccess的时候issueSuccessRedirect,有SavedRequest
例子好像有bug,为何server登录成功后跳转到client,client的OAuth2AuthenticationFilter 执行了两次的拦截,第二次请求oauth2-login时,code为空OAuth2AuthenticationFilter#onLoginSuccess中调用了issueSuccessRedirect(request, response);这个方法不一定使用successUrl:登录成功后如果之前有保存的请求,则重定向到之前的那个请求,否则转到默认的成功页面;这边登录之前访问的是/oauth2-login,so,会拦截两次。
& 上一页 1
jinnianshilongnian
浏览量:1732232
浏览量:2139345
浏览量:4312217
浏览量:177121
浏览量:1222658
浏览量:187877
浏览量:3673965
浏览量:448199
浏览量:472470
感谢楼主,你的文章给我很大的帮助。
Shiro异常1:java.lang.IllegalArgum ...
http://blog.csdn.net/u ...
http://blog.csdn.net/u ...
u 写道 写道Alex丿 写 ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'}

我要回帖

更多关于 网页oauth接口权限 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信