shiro加入到项目里面
(1)创建一个maven模块
(2)导入shiro的依赖包
<!--shiro的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.1</version>
</dependency>
<!--servlet 相关的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
引用:
shiro 模块 -->pom.xml 引入server层
<dependency>
<groupId>cn.dsq</groupId>
<artifactId>crm_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
web 模块 -->pom.xml 引入shiro层
<dependency>
<groupId>cn.dsq</groupId>
<artifactId>crm_shiro</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(3)在web.xml 配置代理过滤器
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(4)新建一个文件applicationContext-shiro.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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!--shiro的核心对象 realm-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置realm-->
<property name="realm" ref="authRealm"/>
</bean>
<!--Realms-->
<bean id="authRealm" class="cn.itsource.shiro.realm.AuthenRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="10"/>
</bean>
</property>
</bean>
<!--shiro的过滤器配置 web.xml的代理过滤器名称一样-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/s/login"/>
<property name="successUrl" value="/s/index"/>
<property name="unauthorizedUrl" value="/s/unauthorized"/>
<property name="filterChainDefinitions">
<value>
/login = anon
/** = authc
</value>
</property>
</bean>
</beans>
(5)在web.xml引入shiro的配置文件
<!-- Spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml,
classpath:applicationContext-shiro.xml
</param-value>
</context-param>
到这里就基本完成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<Employee> {
/**
* 添加租户员工
* @param employee
*/
void addTenantEmployee(Employee employee);
/**
* 通过username查询用户
* @param username
*/
Employee getByUsername(String username);
}
--------------------------------------------------------------------
@Service
public class EmployeeServiceImpl extends BaseServiceImpl<Employee> 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> {
Employee loadByUsername(String username);
}
mapper.xml
<!--Employee loadByUsername(String username);-->
<select id="loadByUsername" parameterType="string" resultType="Employee">
select * from t_employee WHERE username = #{username}
</select>
登录成功之后 无法查询数据。这个什么原因导致的?
cookie的管理机制导致
原因分析
前后端分离项目中,ajax请求没有携带cookie,所以后台无法通过cookie获取到SESSIONID,从而无法获取到session对象。而shiro的认证与授权都是通过session实现的,我们要想办法解决这个问题。
前后端需要建立会话机制
通过token的机制建立前端和后端的会话管理机制
1)登录成功后返回token,并以后每次ajax请求都要携带token
LoginController后台控制器
Employee employee1 = (Employee) currentUser.getPrincipal();
employee.setPassword(null);
Map<String,Object> 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配置文件
<!--session管理器-->
<bean id="sessionManager" class="cn.itsource.shiro.util.CrmSessionManager"/>
<!--shiro的核心对象-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionManager" ref="sessionManager"/>
<!--配置realm-->
<property name="realm" ref="authRealm"/>
</bean>
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);
}
}
}
跨域预检查放行 OPTIONS每次跨域
cors跨域处理时,每次都要跨域预检查,也就是发一个options请求,这种请求shiro应该放行
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="myAuthc" class="cn.itsource.shiro.util.MyAuthenticationFilter"/>
<!--shiro的过滤器配置-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/s/login"/>
<property name="successUrl" value="/s/index"/>
<property name="unauthorizedUrl" value="/s/unauthorized"/>
<property name="filters">
<map>
<entry key="myAuthc" value-ref="myAuthc"/>
</map>
</property>
<property name="filterChainDefinitions">
<value>
/login = anon
/** = myAuthc
</value>
</property>
</bean>
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);
}
}
UserContext保存登录用户
实现
/**
* 当前登录用户相关
*/
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);
}
}