(1)创建一个maven模块
(2)导入shiro的依赖包
org.apache.shiro
shiro-all
1.4.1
javax.servlet
javax.servlet-api
3.0.1
provided
org.springframework
spring-web
4.2.5.RELEASE
shiro 模块 -->pom.xml 引入server层
cn.dsq
crm_service
1.0-SNAPSHOT
web 模块 -->pom.xml 引入shiro层
cn.dsq
crm_shiro
1.0-SNAPSHOT
(3)在web.xml 配置代理过滤器
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
(4)新建一个文件applicationContext-shiro.xml
/login = anon
/** = authc
(5)在web.xml引入shiro的配置文件
contextConfigLocation
classpath:applicationContext.xml,
classpath:applicationContext-shiro.xml
到这里就基本完成shiro加入到分模块开发的maven项目
1.员工密码加密保存
1)已有员工测试类加密保存
规定已有员工密码都是1,通过MD5Util工具加密后存放到数据库
public class MD5Util {
/**盐*/
public static final String SALT = "dsq";
/**
* 加密方法
* @param source
* @return
*/
public static String encrypt(String source){
//加密方式-MD5 加密数据-source - 加盐-SALT 加密次数-10
SimpleHash simpleHash = new SimpleHash("MD5",source,SALT,10);
return simpleHash.toString();
}
//测试法法
public static void main(String[] args) {
System.out.println(encrypt("1"));
}
}
2)没有的员工添加是加密保存
EmployeeController中
@Override
public AjaxResult addOrUpdate(Employee employee) {
//没有id表示是新增
if (employee.getId()==null){
//密码进行加密
employee.setPassword(MD5Util.encrypt(employee.getPassword()));
//保存
employeeService.add(employee);
}else{
//修改
employeeService.update(employee);
}
return AjaxResult.me();
}
1)login.vue
handleSubmit2(ev) {
var _this = this;
this.$refs.ruleForm2.validate((valid) => {
if (valid) {
this.logining = true;
var loginParams = { username: this.ruleForm2.account, password: this.ruleForm2.checkPass };
this.$http.post("/login",loginParams).then(data => {
this.logining = false;
let { message, success, resultObj } = data.data;
if (!success) {
this.$message({
message: message,
type: 'error'
});
} else {
//登录成功跳转/table的路由地址
sessionStorage.setItem('user', JSON.stringify(resultObj));
//修改登录成功后跳转到首页
this.$router.push({ path: '/home' });
}
});
} else {
console.log('error submit!!');
return false;
}
});
}
2)LoginController
@Controller
@CrossOrigin
public class LoginController {
/**
* 身份认证--登录
* @param employee
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public AjaxResult login(@RequestBody Employee employee){
Subject currentUser = SecurityUtils.getSubject();
if(!currentUser.isAuthenticated()){
try {
UsernamePasswordToken token = new UsernamePasswordToken(employee.getUsername(),
employee.getPassword());
currentUser.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("用户名不存在!");
} catch (IncorrectCredentialsException e){
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("密码错误!");
} catch (AuthenticationException e){
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("系统异常!");
}
}
Employee employee1 = (Employee) currentUser.getPrincipal();
employee.setPassword(null);
//除了返回登录成功与否,还要把登录的用户返回前端
return AjaxResult.me().setResultObj(employee1);
}
}
由于前台需要返回用户,所以改造AJaxResult
/**
* Ajax请求的返回内容:增删改
* success:成功与否
* message:失败原因
*/
public class AjaxResult {
private boolean success = true;
private String message = "操作成功!";
private Object resultObj = null;
public boolean isSuccess() {
return success;
}
//链式编程,可以继续. 设置完成后自己对象返回
public AjaxResult setSuccess(boolean success) {
this.success = success;
return this;
}
public String getMessage() {
return message;
}
public AjaxResult setMessage(String message) {
this.message = message;
return this;
}
//默认成功
public AjaxResult() {
}
//失败调用
public AjaxResult(String message) {
this.success = false;
this.message = message;
}
public Object getResultObj() {
return resultObj;
}
public AjaxResult setResultObj(Object resulObj) {
this.resultObj = resultObj;
return this;
}
//不要让我创建太多对象
public static AjaxResult me(){
return new AjaxResult();
}
public static void main(String[] args) {
AjaxResult.me().setMessage("xxx").setSuccess(false);
}
}
3)Realm
/**
* 自定义身份认证Realm
*/
public class AuthenRealm extends AuthenticatingRealm {
@Autowired
private IEmployeeService employeeService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
Employee employee = employeeService.getByUsername(username);
if(employee==null){
throw new UnknownAccountException(username);
}
Object principal = employee;
Object hashedCredentials = employee.getPassword();
ByteSource credentialsSalt = ByteSource.Util.bytes(MD5Util.SALT);
String realmName = getName();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,hashedCredentials,credentialsSalt,realmName);
return info;
}
}
4)Service–Saas租户相关的,没有这个可以不管
public interface IEmployeeService extends IBaseService {
/**
* 添加租户员工
* @param employee
*/
void addTenantEmployee(Employee employee);
/**
* 通过username查询用户
* @param username
*/
Employee getByUsername(String username);
}
--------------------------------------------------------------------
@Service
public class EmployeeServiceImpl extends BaseServiceImpl implements IEmployeeService {
@Autowired
private TenantMapper tenantMapper;
@Autowired
private EmployeeMapper employeeMapper;
@Override
public void addTenantEmployee(Employee employee) {
//获取租户
Tenant tenant = employee.getTenant();
//设置租户注册时间
tenant.setRegisterTime(new Date());
//租户状态
tenant.setState(0);
//添加租户返回租户id 添加前对象里面没有id,添加完成后就有了
tenantMapper.save(tenant);
//把租户id设置给员工
employee.setTenant(tenant);
//在保存员工
employee.setRealName(employee.getUsername());
employeeMapper.save(employee);
}
@Override
public Employee getByUsername(String username) {
return employeeMapper.loadByUsername(username);
}
}
5)Mapper
/**
* 通过继承baseMapper拥有的基础crud,还可以扩展自己方法
*/
public interface EmployeeMapper extends BaseMapper {
Employee loadByUsername(String username);
}
mapper.xml
cookie的管理机制导致
前后端分离项目中,ajax请求没有携带cookie,所以后台无法通过cookie获取到SESSIONID,从而无法获取到session对象。而shiro的认证与授权都是通过session实现的,我们要想办法解决这个问题。
前后端需要建立会话机制
通过token的机制建立前端和后端的会话管理机制
1)登录成功后返回token,并以后每次ajax请求都要携带token
LoginController后台控制器
Employee employee1 = (Employee) currentUser.getPrincipal();
employee.setPassword(null);
Map result = new HashMap<>();
result.put("user",employee1);
System.out.println(currentUser.getSession().getId()+"xxxx");
//登录成功后把会话id返回,会后作为token使用
result.put("token",currentUser.getSession().getId());
return AjaxResult.me().setResultObj(result);
Longin.vue前端登录页面
this.$http.post("/login",loginParams).then(data => {
this.logining = false;
let { success, message, resultObj } = data.data;
if (!success) {
this.$message({
message: message,
type: 'error'
});
} else {
//登录成功跳转/table的路由地址
sessionStorage.setItem('user', JSON.stringify(resultObj.user));
sessionStorage.setItem('token', resultObj.token); //不要加字符串转换了巨大的坑
//修改登录成功后跳转到首页
this.$router.push({ path: '/echarts' });
}
Home.vue前端主页
//退出登录
logout: function () {
var _this = this;
this.$confirm('确认退出吗?', '提示', {
//type: 'warning'
}).then(() => {
sessionStorage.removeItem('user');
sessionStorage.removeItem('token');
_this.$router.push('/login');
}).catch(() => {
});
Main.js
//拦截器
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)
})
2)服务端变为通过token来唯一标识session
Shirospring配置文件
CrmSessionManager
/**
*
* 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,
* 在前后端分离的项目中(也可在移动APP项目使用),我们选择在ajax的请求头中传递sessionId,
* 因此需要重写shiro获取sessionId的方式。
* 自定义CrmSessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
*
*/
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);
}
}
}
cors跨域处理时,每次都要跨域预检查,也就是发一个options请求,这种请求shiro应该放行
/login = anon
/** = myAuthc
MyAuthenticationFilter
/**
* 自定义身份认证过滤器
*/
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);
}
}
实现
/**
* 当前登录用户相关
*/
public class UserContext {
private static final String CURRENT_LOGIN_USER= "loginUser";
/**
* 设置当前登录用户
* @param employee
*/
public static void setUser(Employee employee){
Subject currentUser = SecurityUtils.getSubject();
currentUser.getSession().setAttribute(CURRENT_LOGIN_USER,employee);
}
/**
* 获取当前登录用户
* @return employee
*/
public static Employee getUser(){
Subject currentUser = SecurityUtils.getSubject();
return (Employee) currentUser.getSession().getAttribute(CURRENT_LOGIN_USER);
}
}