基于SpringBoot打造在线教育系统(3)-- 登录与授权管理1

简介: 更正首先抱歉了各位,上一讲有一个错误,我已经在视频里面指出。视频地址:https://www.bilibili.com/video/BV1CA411W7QV/ ,就是junit测试那一块,如果自己写sql,事物是不能加在测试方法上的,还是需要写一个Service,不能偷懒。下次写文,我会更加谨慎,确保不再出现类似的疏漏

更正

首先抱歉了各位,上一讲有一个错误,我已经在视频里面指出。

视频地址:https://www.bilibili.com/video/BV1CA411W7QV/

就是junit测试那一块,如果自己写sql,事物是不能加在测试方法上的,还是需要写一个Service,不能偷懒。下次写文,我会更加谨慎,确保不再出现类似的疏漏。


1. 登录的准备

jpa搞定了,终于要开始做登录了。登录了以后,考虑到用户是有一个身份的,比如管理员,普通用户,vip用户等。


有些功能,比如新增教程,是只有管理员角色才能做的事情,所以,就得弄一套权限管理。


说到权限管理,现在最流行的就是shiro了,据说是spring官方推荐使用的。


那么,还等什么呢,用呗。


这不,兔哥给我推荐了一些资料,我看完后差不多就可以依葫芦画瓢开始了。


2. 建三张表


33c08ea4b73f8fec94002b2ab91adbe8.png

用户可以拥有多个角色,每一个角色拥有多个权限。


我们要做的是一个复杂的,可以商用的在线教育平台,所以不能做的太简单。那么,第一步,权限管理是不能少了。


要做权限管理,我们还缺少3张表,即角色表和权限表,还有一张角色权限关系表。


因为我们使用的是JPA,所以,直接创建class即可。


角色表

package com.edu.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Role {
  @Id
  @GeneratedValue
  private Integer id;
  @Column(length = 20)
  private String name;
  @Column(length = 20)
  private String description;
  public Role(){}
}

权限表

package com.edu.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Permission {
  @Id
  @GeneratedValue
  private Integer id;
  @Column(length = 20)
  private String url;
  @Column(length = 20)
  private String description;
  public Permission(){}

角色权限关系表

package com.edu.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class RolePermission {
  @Id
  @GeneratedValue
  private Integer id;
  @Column
  private Integer roleId;
  @Column
  private Integer permissionId;
  public RolePermission(){}
}

运行主程序,可以看到表已经建好了。

09ff45d8413ae6a23e2b2a90a1b4a1cd.png

为了节省时间,我们直接用insert语句添加初始化数据:

INSERT INTO `edu`.`role` (`id`, `description`, `name`) VALUES ('1', NULL, '管理员');
INSERT INTO `edu`.`role` (`id`, `description`, `name`) VALUES ('2', NULL, '普通会员');
INSERT INTO `edu`.`role` (`id`, `description`, `name`) VALUES ('3', NULL, 'VIP会员');
INSERT INTO `edu`.`permission` (`id`, `description`, `url`) VALUES ('1', '增加分类', 'type:add');
INSERT INTO `edu`.`permission` (`id`, `description`, `url`) VALUES ('2', '修改分类', 'type:edit');
INSERT INTO `edu`.`permission` (`id`, `description`, `url`) VALUES ('3', '删除分类', 'type:delete');
INSERT INTO `edu`.`role_permission` (`id`, `permission_id`, `role_id`) VALUES ('1', '1', '1');
INSERT INTO `edu`.`role_permission` (`id`, `permission_id`, `role_id`) VALUES ('2', '2', '1');
INSERT INTO `edu`.`role_permission` (`id`, `permission_id`, `role_id`) VALUES ('3', '3', '1');

我们做3个测试权限,管理员角色默认拥有所有的权限。

##3. 创建三个Dao

和三个实体类对应,我们需要建立三个dao文件。

2e6610b9e2bcd7bb03eb16fed3062a14.png

代码如下:

public interface RoleDao  extends JpaRepository<Permission, Integer>{
}
public interface PermissionDao  extends JpaRepository<Permission, Integer>{
}
public interface RolePermissionDao  extends JpaRepository<RolePermission, Integer>{
}

老规矩,继承JpaRepository就行了。

##4. 引入Shiro的依赖

用maven引入Shiro的包,在pom.xml中加上:

<!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- shiro-web -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.3.2</version>
        </dependency>

其实我们主要使用的是第一个,第二个后面也许会用到,先加上吧。


##5. 配置Shiro到SpringBoot

因为我们搭建的项目是基于SpringBoot的,所以需要通过配置类的方式,把Bean加入到Spring的Bean工厂。


方法就是,我们自己写一个类, 把Shiro需要用到的Bean声明一下。


创建一个ShiroConfig:

2c780d33d949cfff6f22670391d7686f.png


给这个类加上@Configuration注解,表示它是一个配置类。啥意思呢,我们在不用SpringBoot之前,不是会有一个applicationContext.xml的配置文件吗,在这个文件里面,我们可以写好多Bean的注册。那么被@Configuration注解的类,就是一样的作用。


具体的做法是,我们在ShiroConfig里面添加若干个方法,方法上打一个@Bean注解,意思就和在applicationContext.xml里面配置是一样一样的,被@Bean注解的方法,会返回一个对象,返回的对象会交给Spring来管理,你在别的地方就可以直接使用它了。


Shiro我们只需要学会三个核心组件就可以了。

186d63f8febdd0267f372e64c28d218e.png

##6. 自定义realm

比如我叶小凡来登录了,登录的时候,是不是要验证用户名和密码,这个验证的过程,就需要我们自己来设计。


###6.1 认证

比如我们可以这样设计,先判断用户名是否存在,存在的话怎么样,不存在怎么样?这个就叫做认证,shiro要我们提供一个Realm,把如何认证的代码写了。


###6.2 授权

还有一点,我登录了以后,到底能做哪些事情,我们刚才不是有一个权限表嘛?


7e178f8290332ea3520c5eb0d8c802fb.png

我们配置了三个权限,如果登录成功了,就会进入授权的过程,就是我们需要写一个方法,去数据库里面把这个用户所拥有的权限取出来,然后添加到 授权信息 里面。


这个Realm就是做这两件事情的。


我们创建一个UserRealm,把这两件事情都做了。因为我们使用了Shiro框架,那么就得按照规矩办事,需要去继承一个AuthorizingRealm。

public abstract class AuthorizingRealm extends AuthenticatingRealm
        implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
}

看源码,得知这是一个抽象类,继承了它就必须重写它所有的抽象方法。

我们先注入三个Dao

  @Autowired
  UserDao userDao;
  @Autowired
  PermissionDao permissionDao;
  @Autowired
  RolePermissionDao rolePermissionDao;

###6.3 认证方法

然后,重写认证方法(注意,默认重写两个方法,名字是一样的,下面的那个是认证方法!):

//认证
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("执行了认证");
    //用户名和密码
    UsernamePasswordToken userToken = (UsernamePasswordToken) token;
    //根据用户名去查找这个用户,现在不校验密码
    User user = userDao.findOne(userToken.getUsername());
    if(user == null){
      return null; // 会被认为是用户名不存在
    }
    //Shiro会自动帮我们去做密码的校验,不用你操心
    return new SimpleAuthenticationInfo(user.getUsername(),userToken.getPassword(),getName());
  }

这个方法啥时候被调用呢,调用的时机就是你在其他地方调用 subject.login(token) 的时候。

这也是shiro最让人头疼的地方,就是你自己写了很多代码,却不知道啥时候会调用,挺绕的。


看最后一句:

return new SimpleAuthenticationInfo(userToken.getUsername(),user.getPassword(),getName


返回了一个SimpleAuthenticationInfo,这是一个简单的认证信息,构造函数里面第一个参数是认证对象,你可以传一个Object,比如放一个User对象进去,也可以跟我一样,简单一些,放userName进去,这个东西待会会用到。


第二个参数是密码,我们这边就不加密了,到时候shiro会自己帮你去校验密码是否正确的。注意,这个密码是你从数据库里面查出来的,正确的密码,不是你传进来的密码!!


你可能很奇怪,啥时候校验密码呢?


呵呵,要不怎么用框架呢,shiro连这个校验密码的工作都帮你做了。而且,shiro还可以帮你做很多加密的工作,现在我们就不整那么复杂了,就用明文密码吧。


###6.4 授权方法

重写授权方法(注意,默认重写两个方法,名字是一样的,上面那个是授权方法!):

  //授权
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了授权");
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //获取当前用户
    String userName = principalCollection.getPrimaryPrincipal().toString();
    //根据用户名获取角色
    User userInDB = userDao.findOne(userName);
    String roleId = userInDB.getRoleId();
    //根据角色查询所有的权限
    List<RolePermission> listRolePermission = rolePermissionDao.getByRoleId(Integer.parseInt(roleId));
    Set<String> result = new HashSet<>();
    //遍历所有的权限ID,去获取权限url
    for(RolePermission rolePermission : listRolePermission){
      Integer permissionId = rolePermission.getPermissionId();
      Permission permission = permissionDao.findOne(permissionId);
      result.add(permission.getUrl());
    }
    System.out.println(userName+"的权限包含--" + result);
    info.setStringPermissions(result);
    return info;
  }

思路就是拿到当前登录的数据,得到用户名,注意,那个userName,我们是通过下面的代码拿到的。

String userName = principalCollection.getPrimaryPrincipal().toString();


principalCollection是这个授权方法的参数,我们必须得先在认证方法里面,给SimpleAuthenticationInfo的第一个构造参数赋值,这里才会有。


有没有感受到用框架的魅力呢,所谓框架就是,它把设计写好了,按照作者的思维,先给你把流程跑通,然后在某些需要嵌入具体业务逻辑的地方,做成接口或者抽象方法,给你去完成。


比如这个AuthorizingRealm类,他要求你必须要去继承,然后重写认证和授权方法。我们可以想象,在某个地方,他先调用了认证方法,然后不是得到一个SimpleAuthenticationInfo对象嘛。接着,他又在某个地方调用了授权方法,并且把SimpleAuthenticationInfo对象的principal传进去。


这就是框架的普遍设计思路。


如果你也能理解这些,你也可以去做一些架构的工作了,或者成为全球最大的同性交友网站 —— github的一员。


###6.5 简单挖掘一下源码(如不感兴趣可以直接条过本小节)

既然说到了这里,不挖一下源码是不行了。ok,让我们一起来走一遍。


就稍微读一下认证方法吧,最后返回了这个:

//Shiro会自动帮我们去做密码的校验,不用你操心
return new SimpleAuthenticationInfo(userToken.getUsername(),user.getPassword(),getName()


点进去看:

    /**
     * Constructor that takes in a single 'primary' principal of the account and its corresponding credentials,
     * associated with the specified realm.
     * <p/>
     * This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based
     * on the {@code principal} and {@code realmName} argument.
     *
     * @param principal   the 'primary' principal associated with the specified realm.
     * @param credentials the credentials that verify the given principal.
     * @param realmName   the realm from where the principal and credentials were acquired.
     */
    public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {
        this.principals = new SimplePrincipalCollection(principal, realmName);
        this.credentials = credentials;
    }

看这个:

this.principals = new SimplePrincipalCollection(principal, realmName);


这说明这个类有一个属性principals,它的值是SimplePrincipalCollection,两个构造参数,分别是principal(我们这次传递的是String,假如说是“admin”)。进入SimplePrincipalCollection:

    public SimplePrincipalCollection(Object principal, String realmName) {
        if (principal instanceof Collection) {
            addAll((Collection) principal, realmName);
        } else {
            add(principal, realmName);
        }
    }

因为我们传的不是集合,而是String,所以走到else

    public void add(Object principal, String realmName) {
        if (realmName == null) {
            throw new IllegalArgumentException("realmName argument cannot be null.");
        }
        if (principal == null) {
            throw new IllegalArgumentException("principal argument cannot be null.");
        }
        this.cachedToString = null;
        getPrincipalsLazy(realmName).add(principal);
    }

两个值都不是null,走到

getPrincipalsLazy(realmName).add(principal);

继续挖:

    protected Collection getPrincipalsLazy(String realmName) {
        if (realmPrincipals == null) {
            realmPrincipals = new LinkedHashMap<String, Set>();
        }
        Set principals = realmPrincipals.get(realmName);
        if (principals == null) {
            principals = new LinkedHashSet();
            realmPrincipals.put(realmName, principals);
        }
        return principals;
    }

意思就是先设置了一下Realm,然后返回principals,principals是一个Set集合。然后再去add,把我们本次添加的principal,也就是“admin”字符串加进去。

又因为这一句:

 realmPrincipals.put(realmName, principals);

所以,最终principals会多一个principal = “admin”。


##7. ShiroConfig实现

分三步,分别是

####1.创建realm对象,连接数据库,做认证和授权

####2.将自定义realm交给SecurityManager管理所有用户

####3.创建Shiro工厂,设置需要过滤的对象

直接贴代码:

package com.edu.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
  //1.创建realm对象,连接数据库,做认证和授权
  @Bean
  public UserRealm userRealm(){
    return new UserRealm();
  }
  //2.将自定义realm交给SecurityManager管理所有用户
  @Bean(name="securityManager")
  public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
    DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    //关联Userrealm
    defaultWebSecurityManager.setRealm(userRealm);
    return defaultWebSecurityManager;
  }
  //3.ShiroFilterFactoryBean  创建Shiro工厂,设置需要过滤的对象
  @Bean
  public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //设置案权管理器
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    //添加过滤器
    /**
     * anon:无需认证
     * authc:必须认证
     * role:拥有某个角色权限才能访问
     * perms:拥有某个资源权限才能访问
     * user:必须拥有记住我功能才能用
     */
    Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/admin/*", "authc");  //所有带admin的请求都要验证登录
    //关于分类的权限操作
    filterChainDefinitionMap.put("/type/add", "perms[type:add]");  
    filterChainDefinitionMap.put("/type/edit", "perms[type:edit]");  
    filterChainDefinitionMap.put("/type/delete", "perms[type:delete]");  
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    //设置登录的请求 -- 跳转到登录页面
    shiroFilterFactoryBean.setLoginUrl("/view/login");
    //设置未授权页面 -- 就会默认跳转到未授权页面
    //shiroFilterFactoryBean.setUnauthorizedUrl("/view/noauth");
    return shiroFilterFactoryBean;
  }
}

##8.UserController

终于创建用户控制器了,里面就有一个登录的方法:

package com.edu.controller;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
  @RequestMapping("/user/login")
  @ResponseBody
  public Map<String,Object> login(String userName,String password,Model m){
    Map<String,Object> result = new HashMap<>();
    result.put("code",0);
    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    //得到登录的数据
    UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
    try{
      subject.login(token); //就是执行登录的方法,如果没有异常,就是登录成功!
      //能走到这就说明登录成功了
      return result;
    }catch(UnknownAccountException e){
      result.put("code",-1);
      result.put("msg", "用户名不存在!");
      return result;
    }catch(IncorrectCredentialsException e){
      result.put("code",-1);
      result.put("msg", "密码错误!");
      return result;
    }
  }
}

这个控制器需要配合login.jsp来完成一个完整的登录功能。

登录的jsp页面我做了一些调整,使得可以正确和这个项目对接,代码太多我就不贴了,有需要的去群文件自取。

相关文章
|
20天前
|
Web App开发 编解码 Java
B/S基层卫生健康云HIS医院管理系统源码 SaaS模式 、Springboot框架
基层卫生健康云HIS系统采用云端SaaS服务的方式提供,使用用户通过浏览器即能访问,无需关注系统的部署、维护、升级等问题,系统充分考虑了模板化、配置化、智能化、扩展化等设计方法,覆盖了基层医疗机构的主要工作流程,能够与监管系统有序对接,并能满足未来系统扩展的需要。
46 4
|
2天前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码B/S架构+springboot三级公立医院绩效考核系统源码 医院综合绩效核算系统源码
作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
21 2
|
3天前
|
运维 监控 Java
springboot基层区域HIS系统源码
医疗(医院)机构正式使用云HIS系统之前,要先进行院内基础数据的配置,主要在数据管理模块中进行,由系统管理员来操作。
10 0
|
4天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
46 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
22天前
|
小程序 JavaScript Java
基于SpringBoot+Vue+uniapp微信小程序的4S店客户管理系统的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的4S店客户管理系统的详细设计和实现
44 4
|
22天前
|
小程序 JavaScript Java
基于SpringBoot+Vue+uniapp微信小程序的汽车保养系统的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的汽车保养系统的详细设计和实现
10 1
|
22天前
|
小程序 JavaScript Java
基于SpringBoot+Vue+uniapp微信小程序的个人行政复议在线预约系统的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的个人行政复议在线预约系统的详细设计和实现
29 1
|
22天前
|
小程序 JavaScript Java
基于SpringBoot+Vue+uniapp微信小程序的医院核酸检测服务系统的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的医院核酸检测服务系统的详细设计和实现
39 0
|
1月前
|
Java Linux
Springboot 解决linux服务器下获取不到项目Resources下资源
Springboot 解决linux服务器下获取不到项目Resources下资源
|
1月前
|
Java API Spring
SpringBoot项目调用HTTP接口5种方式你了解多少?
SpringBoot项目调用HTTP接口5种方式你了解多少?
97 2