Bootstrap

Shiro加入到Maven项目

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);
    }
}
;