这次的分享记录得非常的详细,希望对大家有所帮助!
2.1 导入shiro需要的jar包(这里web模块也要依赖shiro模块)
cn.itsource
itsource_service
1.0-SNAPSHOT
org.apache.shiro
shiro-all
1.4.1
javax.servlet
javax.servlet-api
3.0.1
provided
2.2 在web.xml配置文件中加入过滤器
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
2.3 准备shiro spring配置文件
(applicationContext-shiro.xml)
Itsource_auth_shiro中的applicationContext-shiro.xml
/login = anon
/** = authc
2.4 把shiro配置文件(applicationContext-shiro.xml)集成到Spring(这里注意集成到的是Spring而不是SpringMvc)
contextConfigLocation
classpath*:applicationContext.xml,
classpath*:applicationContext-shiro.xml
org.springframework.web.context.ContextLoaderListener
3.1. ==密码加密保存工具 (这里用的是MD5加密方式) ==
package cn.fours.util;
import org.apache.shiro.crypto.hash.SimpleHash;
public class MD5Util {
public static final String SALT = "itsource";
/**
* 加密
*
* @param source
* @return
*/
public static String encrypt(String source) {
SimpleHash simpleHash = new SimpleHash("MD5", source, SALT, 10);
return simpleHash.toString();
}
}
3.2 在添加调用添加员工时添加加密方式密码
employee.setPassword(MD5Util.encrypt(employee.getPassword()));
3.3 前端vue登录实现(这里的页面是最终的页面,包括了最后第三方登录的,注意的是我这里的图片是用的绝对路径,引用时一定要注意删掉不然会报错)
系统登录
记住密码
没有账号?注册
登录
其它登录方式?
3.4 LoginController(这里的登录模块包括了普通用户名密码登录+微信第三方登录+注册+绑定+免密登录,重定向的页面下面会给出详细代码)
还有要注意的就是因为cookie的管理机制导致前后端分离项目中,ajax请求是没有携带cookie的。所以后台是无法通过cookie获取ssionid的,从而无法获取到session对象,下面代码也是写了解决方法的(Token)
package cn.fours.web.controller;
import cn.fours.common.domain.Login;
import cn.fours.common.domain.WxUser;
import cn.fours.service.ILoginService;
import cn.fours.service.IWxUserService;
import cn.fours.util.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 微信登录
*/
@Controller
@CrossOrigin
public class WxLoginController {
@Autowired
private IWxUserService service;
@Autowired
private ILoginService loginService;
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public JsonResult login(@RequestBody Login login){
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()){
try {
MyUsernamePasswordToken token = new MyUsernamePasswordToken(login.getUsername(),login.getPassword());
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
return new JsonResult("用户名不存在!");
} catch (IncorrectCredentialsException e){
e.printStackTrace();
return new JsonResult("密码错误!");
} catch (AuthenticationException e){
e.printStackTrace();
return new JsonResult("你登个鸡巴!");
}
}
JsonResult jsonResult = new JsonResult();
Login login1 = (Login) subject.getPrincipal();
login.setPassword(null);
HashMap map = new HashMap<>();
map.put("user",login1);
map.put("token",subject.getSession().getId());
jsonResult.setResultObj(map);
return jsonResult;
}
@RequestMapping(value = "/register", method = RequestMethod.POST)
@ResponseBody
public JsonResult register(@RequestBody Login login) {
JsonResult result = new JsonResult();
//密码一致验证
/* if (!Objects.equals(login.getPassword(), comfirmPassword)) {
result.setMessage("密码不一致");
result.setSuccess(false);
return result;
}*/
login.setPassword(MD5Util.encrypt(login.getPassword()));
String password = login.getPassword();
String encrypt = MD5Util.encrypt(password);
login.setPassword(encrypt);
try {
loginService.save(login);
} catch (Exception e){
result.setSuccess(false);
result.setMsg("注册失败,请重试");
e.printStackTrace();
}
return result;
}
/**
* 拉起二维码
* 跳转到本地的login
* @return
*/
@RequestMapping(value = "/wxlogin",method =RequestMethod.GET )
@ResponseBody
public String login(Model model){
System.out.println("242424");
String url = WxConstants.CODEURL.replaceAll("APPID", WxConstants.APPID).
replaceAll("CALLBACK", WxConstants.CALLBACK)
.replaceAll("SCOPE", WxConstants.SCOPE);
model.addAttribute("wxLoginUrl",url);
return url;
}
@RequestMapping(value = "/callback",method =RequestMethod.GET )
public String callBack(String code,String state) throws Exception {
/**
* 获取code
*发送请求获取ak
*/
String akUrl = WxConstants.ACCESSTOKEURL.replaceAll("APPID",
WxConstants.APPID).replaceAll("SECRET", WxConstants.APPSECRET)
.replaceAll("CODE", code);
/**
* 发送请求获取ak
*/
String responseStr = HttpClientUtils.httpGet(akUrl, null);
/**
* 发送请求,获取用户信息''
* json字符串转换为对象
*/
JSONObject jsonobject = (JSONObject)JSON.parse(responseStr);
String access_token = jsonobject.getString("access_token");
String openid = jsonobject.getString("openid");
System.out.println("openid:"+jsonobject.getString("openid"));
/**
* 拿到ak和openid再发送请求
* 发送用户信息的请求地址
*/
String userInfoUrl = WxConstants.USERINFOURL.replaceAll("ACCESS_TOKEN", access_token).replaceAll("OPENID", openid);
String userInfoStr = HttpClientUtils.httpGet(userInfoUrl, null);
/**
* 判断用户是否绑定过,没有绑定就跳转到绑定页面(用户名和密码)
* 根据openid进行查询用户
*/
JSONObject userInfo = (JSONObject)JSON.parse(userInfoStr);
String openid1 = userInfo.getString("openid");
WxUser wxuser = service.findWxUserById(openid);
if(wxuser==null){
String nickname = userInfo.getString("nickname");
System.out.println("nickname:"+nickname);
String headimgurl = userInfo.getString("headimgurl");
String unionid = userInfo.getString("unionid");
wxuser = new WxUser();
//保存到数据库
wxuser.setOpenid(openid1);
wxuser.setNickname(nickname);
wxuser.setHeadimgurl(headimgurl);
wxuser.setUnionid(unionid);
service.save(wxuser);
//跳转到绑定页面,绑定一个用户
return "redirect:http://localhost:8080/#/bind?openid="+openid;
}else {
//说明有这个人,登录过得,如果没有绑定,进行绑定,如果绑定过就免密登录
if(wxuser.getEmpid()!=null){
//查询单个绑定数据
Login login = loginService.queryOne(wxuser.getEmpid());
//传name参数,免密只需要name
MyUsernamePasswordToken token = new MyUsernamePasswordToken(login.getUsername());
Subject subject = SecurityUtils.getSubject();
subject.login(token);
Login mmlogin = (Login) subject.getPrincipal();
Serializable tokenid = subject.getSession().getId();
//扫码免密登录成功之后直接跳转到登陆成功页面
return "redirect:http://localhost:8080/#/mianmi?user="+mmlogin+"tokenid="+tokenid;
}
}
return null;
}
//没有登录过就绑定用户(绑定方法)
@RequestMapping(value = "/binder",method = RequestMethod.POST)
@ResponseBody
public JsonResult binder(@RequestBody Map map ){
//拿到用户名,密码,openid
String username = map.get("username");
String password = map.get("password");
String openid = map.get("openid");
//根据openid去查询
WxUser wxUser = service.findWxUserById(openid);
if(wxUser!=null){
//根据username查询用户id(还没有写这一个方法)
/*WxLoginController login = loginService.getByUsername(username);*/
Login byUsername = loginService.getByUsername(username);
Long id = byUsername.getId();
wxUser.setEmpid(id);
//更新,绑定之后
service.update(wxUser);
//免密登录---认证和授权
return new JsonResult("绑定成功");
}
return new JsonResult("绑定失败");
}
}
3.5 退出登录
sessionStorage.removeItem('token');
3.6 Token放在请求体里面,所以在前端main.js中主动携带Token
axios.interceptors.request.use(config => {
if (sessionStorage.getItem('token')) {
// 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
config.headers['X-Token'] = sessionStorage.getItem('token')
}
console.debug('config',config)
return config
}, error => {
// Do something with request error
Promise.reject(error)
})
3.7 applicationContext-shiro.xml
Shirospring配置文件
3.8 CrmSessionMannager
package cn.fours.util;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
public class CrmSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "X-TOKEN";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public CrmSessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//取到jessionid
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
HttpServletRequest request1 = (HttpServletRequest) request;
//如果请求头中有 X-TOKEN 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
System.out.println(id+"jjjjjjjjj"+request1.getRequestURI()+request1.getMethod());
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
3.9 这里前后端分离是会出现跨域的,所以在预检请求放行OPTIONS (cors跨域处理时,每次都要跨域预检查,也就是发一个options请求,只要是这样的请求shiro都是要放行的)
/login = anon
/** = myAuthc
3.10 自定义身份认证过滤器
package cn.fours.util;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* 登录认证Token的处理
*/
public class MyAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//如果是OPTIONS请求,直接放行
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String method = httpServletRequest.getMethod();
System.out.println(method);
if("OPTIONS".equalsIgnoreCase(method)){
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
//薪增方法
@Override
protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) {
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
String loginType = LoginType.PASSWORD;
if(request.getParameter("loginType")!=null && !"".equals(request.getParameter("loginType").trim())){
loginType = request.getParameter("loginType");
}
return new MyUsernamePasswordToken(username, password,loginType,rememberMe,host);
}
}
4.1 工具类的封装
package cn.fours.util;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class HttpClientUtils {
/**
* http请求工具类,post请求
*
* @param url url
* @param params json字符串的参数
* @return
* @throws Exception
*/
public static String httpPost(String url, String params) throws Exception {
// 创建httpClient对象
DefaultHttpClient defaultHttpClient = null;
try {
defaultHttpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json;charset=ut-8");
if (params != null) {
System.out.println("请求参数:" + params);
// 设置请求参数
HttpEntity httpEntity = new StringEntity(params, "utf-8");
httpPost.setEntity(httpEntity);
}
// 执行post请求,并得到相应结果
HttpResponse httpResponse = defaultHttpClient.execute(httpPost);
if (httpResponse.getStatusLine().getStatusCode() != 200) {
String errorLog = "请求失败,errorCode:" + httpResponse.getStatusLine().getStatusCode();
throw new Exception(url + errorLog);
}
// 解析结果
HttpEntity responseEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(responseEntity, "utf-8");
System.out.println("请求结果:" + responseStr);
return responseStr;
} catch (ClientProtocolException e) {
e.printStackTrace();
throw e;
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
if (defaultHttpClient != null)
defaultHttpClient.getConnectionManager().shutdown();
}
}
/**
* http请求工具类,get请求
*
* @param url 请求地址:可以已经带参数(?),也可以没有带参数,在params中传过来
* @param params 参数:值支持字符串和list
* @return
* @throws Exception
*/
public static String httpGet(String url, Map params) throws Exception {
DefaultHttpClient defaultHttpClient = null;
try {
defaultHttpClient = new DefaultHttpClient();
if (params != null) {
// 参数的拼接
StringBuilder stringBuilder = new StringBuilder();
Iterator iterator = params.keySet().iterator();
String key;
while (iterator.hasNext()) {
key = iterator.next();
Object val = params.get(key);
if (val instanceof List) {
// 如果是list,则遍历拼接
List v = (List) val;
for (Object o : v) {
stringBuilder.append(key).append("=").append(o.toString()).append("&");
}
} else {
// 字符串:直接拼接
stringBuilder.append(key).append("=").append(val.toString()).append("&");
}
}
// 删除最后一个&
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
if (url.indexOf("?") > 0) {
// url地址本身包含?
url = url + "&" + stringBuilder.toString();
} else {
url = url + "?" + stringBuilder.toString();
}
}
System.out.println("请求地址:" + url);
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Content-Type", "application/json;charset=ut-8");
// 执行
HttpResponse httpResponse = defaultHttpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() != 200) {
String errorLog = "请求失败,errorCode:" + httpResponse.getStatusLine().getStatusCode();
throw new Exception(url + errorLog);
}
// 解析结果
HttpEntity responseEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(responseEntity, "utf-8");
System.out.println("请求结果:" + responseStr);
return responseStr;
} catch (ClientProtocolException e) {
e.printStackTrace();
throw e;
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
if (defaultHttpClient != null)
defaultHttpClient.getConnectionManager().shutdown();
}
}
}
4.2 常量封装
package cn.fours.util;
public class WxConstants {
/**
* 应用程序id
*/
public final static String APPID = "wxd853562a0548a7d0";
//用户授权后微信的回调域名
public final static String CALLBACK="http://bugtracker.itsource.cn/callback";
/**
* pc端常量
*/
public final static String SCOPE = "snsapi_login";
/**
* 应用密码
*/
public final static String APPSECRET = "4a5d5615f93f24bdba2ba8534642dbb6";
//微信上获取code的地址---拉起二维码
public final static String CODEURL = "https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=CALLBACK&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
//微信上获取ak的地址
public final static String ACCESSTOKEURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
//微信上获取用户信息的地址
public final static String USERINFOURL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
}
4.2.1 重写登录密码匹配器
package cn.fours.util;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
/**
*
* 重写密码登录匹配器
*/
public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
MyUsernamePasswordToken mupt = (MyUsernamePasswordToken) token;
if (mupt.getLoginType().equals(LoginType.NOPASSWD)) {
//免密登录
return true;
}
if (mupt.getLoginType().equals(LoginType.PASSWORD)) {
//密码登录
return super.doCredentialsMatch(token, info);
} else {
return false;
}
}
}
4.2.2 配置登录类型常量
package cn.fours.util;
//登录类型常量
public class LoginType {
public static final String NOPASSWD = "NoPassword";
public static final String PASSWORD = "Password";
}
4.2.3 在自定义身份认证过滤器(MyAuthenticationFilter )新增方法
//薪增方法
@Override
protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) {
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
String loginType = LoginType.PASSWORD;
if(request.getParameter("loginType")!=null && !"".equals(request.getParameter("loginType").trim())){
loginType = request.getParameter("loginType");
}
return new MyUsernamePasswordToken(username, password,loginType,rememberMe,host);
}
}
4.2.4 重写UsernamePasswordToken(MyUsernamePasswordToken )
package cn.fours.util;
import org.apache.shiro.authc.UsernamePasswordToken;
public class MyUsernamePasswordToken extends UsernamePasswordToken {
private String loginType;
public MyUsernamePasswordToken() {
super();
}
/**账号密码登录*/
public MyUsernamePasswordToken(String username, String password, String loginType, boolean rememberMe, String host) {
super(username, password, rememberMe, host);
this.loginType = loginType;
}
/**免密登录*/
public MyUsernamePasswordToken(String username) {
super(username, "", false, null);
this.loginType = LoginType.NOPASSWD;
}
//需要用户名和密码登录
public MyUsernamePasswordToken(String username, String password) {
super(username, password, false, null);
this.loginType = LoginType.PASSWORD;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
==4.3 bind前端页面 ==
系统登录
绑定
没有账号?注册
4.4 register前端页面
员工注册
点击上传
注册
重置
4.5 mianmi前端页面(这个页面只是我免密登录时需要携带Token数据和用户名,所以相当于一个跳板,在页面加载之前执行方法跳转到主页),这里不建议这样做,很不安全,地址完全暴露,但是实在是没有想到其它好的办法