(1)加密工具的抽取
import org.apache.shiro.crypto.hash.SimpleHash;
public class MD5Util {
//加密方式
public static final String ALGORITHMNAME = "MD5";
//盐值
public static final String SALT ="source";
//加密次数
public static final int HASHITERATIONS = 10;
//把密码传进来进行加密
public static String createMD5(String password){
SimpleHash hash = new SimpleHash(ALGORITHMNAME,password,SALT,HASHITERATIONS);
return hash.toString();
}
}
(2)自定义一个类实现AuthorizingRealm,它有两个方法,doGetAuthenticationInfo身份验证和doGetAuthorizationInfo授权
我们需要从写里面的逻辑
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
public class MyRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService ;
@Override//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override//身份认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//判断当前登录用户账号是否存在
String username = (String)token.getPrincipal();
//从数据根据名字取查询有没有这个人物
Employee employee =employeeService.findByUsername(username);
if(employee==null){
return null ;
}
//判断密码是否正确
//加盐
ByteSource source = ByteSource.Util.bytes(MD5Util.SALT);
//第一个参数用户的身份,第二个是数据库查询的密码,第三个加盐,第四个当前realm的名字
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(employee, employee.getPassword(), source, getName());
return info ;
}
}
(3)applicationContext-shiro的配置,注意在applicationContext中引用这个配置,还有shiro的监听器要和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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<bean id="myRealm" class="cn.yhh.realm.MyRealm">
<property name="name" value="myRealm"/>
<!-- 配置密码匹配器 -->
<property name="credentialsMatcher">
//自定义的验证器,第三方登录中需要跳过密码的验证
<bean class="cn.yhh.realm.RetryLimitHashedCredentialsMatcher">
<!--认证方式-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次数-->
<property name="hashIterations" value="10"/>
</bean>
</property>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--如果没有认证,都跳到loginUrl对应的路径-->
<property name="loginUrl" value="/login"/>
<!--如果认证通过之后,就跳到successUrl对应的路径-->
<property name="successUrl" value="/s/success.jsp"/>
<!--如果你访问某个资源,没有权限,就跳到unauthorizedUrl对应的路径中-->
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/code.html = anon
/logout = logout
/wechat/callback=anon//微信跳转需要的路径
/static/** = anon
/** = user //表示携带User对象的用户可以访问所有资源
</value>
</property>
</bean>
</beans>
4)微信登录
- 因为三方登录是不要进行密码的验证的,所有我们自定义一个类去继承HashedCredentialsMatcher(在shiro.xml中配置),我们只需要用户授权就可以,不需要进行验证密码,
但是我们用shiro 登录需要拿到一个token的令牌,所以我们又要自定义一个类去覆写UsernamePasswordToken,为了方便可以定义一个枚举表示当前的登录类型
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
EasyTypeToken tk = (EasyTypeToken) authcToken;
//如果是免密登录直接返回true
if(tk.getType().equals(LoginType.NOPASSWD)){
return true;
}
//不是免密登录,调用父类的方法
return super.doCredentialsMatch(tk, info);
}
}
import org.apache.shiro.authc.UsernamePasswordToken;
public class EasyTypeToken extends UsernamePasswordToken {
//这随便写的吧应该都可以
private static final long serialVersionUID = -2564928913725078138L;
private LoginType type;//表示当前登录的类型
public EasyTypeToken() {
super();
}
public EasyTypeToken(String username, String password, LoginType type, boolean rememberMe, String host) {
super(username, password, rememberMe, host);
this.type = type;
}
/**免密登录*/
public EasyTypeToken(String username) {
super(username, "", false, null);
this.type = LoginType.NOPASSWD;
}
/**账号密码登录*/
public EasyTypeToken(String username, String password) {
super(username, password, false, null);
this.type = LoginType.PASSWORD;
}
public LoginType getType() {
return type;
}
public void setType(LoginType type) {
this.type = type;
}
}
public enum LoginType {
PASSWORD("password"), // 密码登录
NOPASSWD("nopassword"); // 免密登录
private String code;// 状态值
private LoginType(String code) {
this.code = code;
}
public String getCode () {
return code;
}
}
- 微信登录需要去微信卡发着平台申请拿到最主要的appid和secret
在页面我们需要引入他的js文件
<script src="http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
<!--定义一个容器装二维码-->
<div id="login_container">
</div>
</body>
<script>
var obj = new WxLogin({
self_redirect:false,
id:"login_container",/*容器的id*/
appid: "你自己申请的",
scope: "snsapi_login",
redirect_uri: "",//响应授权码的地址-第一部分是申请下来的地址,第二部分是跳转后台的路径
state: "xxx",----状态,多用于判断当前登录的状态是否安全
style: "white", ---二维码的颜色
href: ""
});
</script>
- 导入jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
跳转后台的controller
import com.alibaba.fastjson.JSON;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@Controller
public class WxCharController {
@Autowired
private IEmployeeService employeeService ;
@RequestMapping("/wechat/callback")
public String callback(String code , String state){
//这里可以判断state是否改变判断此次请求是否安全
//得到code换取token的地址
String getTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=你自己申请的&secret=和appid一样&code="+code+"&grant_type=authorization_code";
//得到地址后通过工具类得到token
String tokenResult = HttpClientUtil.doGet(getTokenUrl);
System.out.println(tokenResult);
//得到的token是json格式,将他转成map,得到换取用户资源的openid和access_token
Map<String,String> tokenResultMap = JSON.parseObject(tokenResult,Map.class);
//拿到token之后,换取用户资源
String tokenString = tokenResultMap.get("access_token");
String openId = tokenResultMap.get("openid");
String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+tokenString+"&openid="+openId;
//拿到了用户的基本信息
String userResultStr = HttpClientUtil.doGet(userInfoUrl);
System.out.println(userResultStr);
//根据opoenId去数据库获取user对象
Employee employee =employeeService.findByOpenid(openId);
//如果user不为空 ,代码里面直接调用shiro的登录 -> 直接跳转主页
if(employee!=null){
String username = employee.getUsername();
//这里用的就是我覆写的,并且通过自定义的拦截器,跳过密码的验证
EasyTypeToken token = new EasyTypeToken(username);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
//从session中拿到用户对象
UserContext.setUserInSession(employee);
return "main";
}else {
//如果user为空,说明这个openid没有绑定过,那么久要跳转到绑定页面
}
//绑定页面显示两种情况 : 绑定已有账号 ; 绑定新注册账号 ,把openId传入到页面
//如果提交的是已有账号绑定 用户名 ,密码 ,openId
//带着用户名和密码去数据库中查询 ,如果查到了User ,就修改User.openId 设置进去 ,update.user
//如果是提交注册 :用户名 ,密码 ,验证码 ,手机号 , openId
//先做注册操作 insert.user ,把openId和user信息一并保存
return null ;
抽取通过地址获得token的工具
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class HttpClientUtil {
// http://bugtracker.itsource.cn/wechat/callback?code=222&state=99
// http://bugtracker.itsource.cn/wechat/callback code=222&state=99
public static String doGet(String uri) {
//1:创建一个HttpClient的实例
CloseableHttpClient httpclient = HttpClients.createDefault();
//2:创建一个get请求实例
HttpGet httpGet = new HttpGet(uri);
//请求的响应:
CloseableHttpResponse response1 = null;
try {
//3:使用HttpClient的实例执行get请求
response1 = httpclient.execute(httpGet);
//http请求的状态:404 500 200
System.out.println(response1.getStatusLine());
int statusCode = response1.getStatusLine().getStatusCode();
if (statusCode == 200) {
//请求成功:
HttpEntity entity1 = response1.getEntity();
String result = EntityUtils.toString(entity1, "utf-8");
System.out.println(result);
return result;
} else {
//请求失败:自己做自己的业务逻辑
System.out.println("请求失败......:"+statusCode);
}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}