Springboot 系列

Shiro:连接数据库实现认证、授权

2021-01-08  本文已影响0人  掌灬纹

Apache Shiro 一个简单的Java安全框架,在此框架基础上可以轻松实现网站的用户认证授权

一、用户认证与拦截器

1.SpringBoot项目准备

    <dependencies>
        <!--mybatis druid数据原-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>


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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--整合thymeleaf-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
    </dependencies>
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver

    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
  type-aliases-package: com.ht.springbootshiro01.pojo
  mapper-locations: classpath:mapper/*.xml
# 资源位置resources/mapper/*.xml
(1)Dao(Mapper)层逻辑代码(省略实体类)
@Repository
@Mapper
// Dao 层
public interface UserMapper {

    /**
     * 根据用户名 查询指定用户
     * @param name
     * @return
     */
    public User queryUserByName(@Param("name") String name);
}
(2)Mapper.xml文件编写(查询数据库)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ht.springbootshiro01.mapper.UserMapper">

    <select id="queryUserByName" parameterType="String" resultType="User">
        select * from mybatis.user where name=#{name};
    </select>

</mapper>
(3)Service层业务逻辑(调用Dao层)
@Service
public class UserService {
    // service 调用 dao层
    @Autowired
    UserMapper userMapper;

    public User queryUserByName(String name){
        return userMapper.queryUserByName(name);
    }
}
(4)测试代码(查询用户结果)
@SpringBootTest
class SpringbootShiro01ApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        // mybatis 测试
        User admin = userService.queryUserByName("admin");
        System.out.println(admin.toString());
    }
}

至此查询成功后,SpringBoot 整合MyBatis数据库准备工作完成

2.基础网页路由配置

/**
 * 管理路由跳转的Controller
 */
@Controller
public class RouteController {

    @RequestMapping({"/index","/"})
    public String index(Model model){
        model.addAttribute("msg","Hello Shiro");
        return "index";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    //user
    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    // show
    @RequestMapping("/show")
    public String show(){
        return "show";
    }

}

3.编写Shiro配置文件,自定义Realm(管理授权、认证)

@Configuration
public class ShiroConfig {

    //1.创建Realm对象 自定义 注入bean
    @Bean(name = "getUserRealm")
    public UserRealm getUserRealm(){
        return new UserRealm();
    }

    //2.SecurityManager Spring 注入参数 bean Qualifier(方法名)
    @Bean(name = "getSecurityManager")
    public DefaultWebSecurityManager defaultSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联 Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //3.Shiro Filter
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("getSecurityManager") DefaultSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置 安全管理
        bean.setSecurityManager(securityManager);
        //过滤器配置
        Map<String, String> filterMap = new LinkedHashMap<>();

        // /user 请求 拦截 认证后可访问
        filterMap.put("/user/**","authc");

        // 开放登录接口 首页
        filterMap.put("/toLogin","anon");
        filterMap.put("/index","anon");
        filterMap.put("/","anon");

        // 关闭其它路由 需授权访问
        //filterMap.put("/**","authc");

        bean.setFilterChainDefinitionMap(filterMap);
        //System.out.println("Shiro拦截器注入成功");

        // 自定义登录页面路径 实现登录拦截
        bean.setLoginUrl("/toLogin");

        return bean;
    }

}

至此实现了Shiro的初步登录拦截,发现user/下的路由都不能访问

4.编写自定义Realm规则,认证 继承AuthorizingRealm

~ token令牌实际应用规则:获取前端登录传递的用户名、密码信息封装令牌,Shiro框架会自动经过自定义Realm 认证规则检验,与数据库中用户信息匹配比较

// 自定义UserRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行了 授权 ==》" + principals.getRealmNames().toString());
        return null;
    }

    // token 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了 认证 ==》" + token.getPrincipal().toString());

        // 数据库取数据 进行认证
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        // 前端传递 用户名 查询 数据库
        User user = userService.queryUserByName(userToken.getUsername());

        if (null == user){
            // 前端传递 与 数据库查询用户名 不一致
            return null; // 抛出异常 未知用户名
        }

        // 默认密码不加密(不安全) 可以做 md5加密 或 md5盐值加密
        // 密码认证 shiro做
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}
@Controller
public class LoginController {

    /**
     * 登录请求 获取表单传递数据 封装令牌 token
     * @param username
     * @param password
     * @param model
     * @return
     */
    @PostMapping("/login.do")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Model model){
        System.out.println("登录信息:username: " + username + " ; password: " + password);
        // 获取当前用户
        Subject user = SecurityUtils.getSubject();
        // 封装令牌 用于 Realm验证
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);

        // 执行登录
        try {
            user.login(token); // 调用自定义 UserRealm 认证方法
            return "index";
        } catch (UnknownAccountException uae) {
            // 用户不存在
            System.out.println("There is no user with username of " + token.getPrincipal());
            model.addAttribute("msg","用户名不存在");
            return "login";
        } catch (IncorrectCredentialsException ice) {
            // 密码错误
            System.out.println("Password for account " + token.getPrincipal() + " was incorrect!");
            model.addAttribute("msg","密码错误");
            return "login";
        } catch (LockedAccountException lae) {
            // 当前用户锁定
            System.out.println("The account for username " + token.getPrincipal() + " is locked.  " +
                    "Please contact your administrator to unlock it.");
            model.addAttribute("msg","用户锁定");
            return "login";
        } catch (AuthenticationException ae) {
            //unexpected condition?  error?
            System.out.println("其它错误");
            model.addAttribute("msg","其它错误");
            return "login";
        }

    }
}

~ 测试Shiro的用户认证


测试认证.jpg

二、用户授权与前端整合

1.FilterMap中添加指定路由权限

       Map<String, String> filterMap = new LinkedHashMap<>();
        // 管理员开放权限
        //filterMap.put("/user/**","perms[admin]");
        // 授权 只有 拥有 user:add的权限才可访问 失败401 未授权
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");

        // /user 请求 拦截 认证后可访问
        filterMap.put("/user/**","authc");

        // 开放登录接口 首页
        filterMap.put("/toLogin","anon");
        filterMap.put("/index","anon");
        filterMap.put("/","anon");
        // 关闭其它路由 需授权访问
        //filterMap.put("/**","authc");

        bean.setFilterChainDefinitionMap(filterMap);
        //System.out.println("Shiro拦截器注入成功");

        // 自定义登录页面路径 实现登录拦截
        bean.setLoginUrl("/toLogin");
        // 设置未授权请求
        bean.setUnauthorizedUrl("/unauthorized");

2.编写定制未授权跳转页面及路由控制器

/***
 * 未授权控制器
 */
@Controller
public class UnauthorizedController {
    /**
     * 未授权页面
     * @return
     */
    @RequestMapping("/unauthorized")
    public String toUnauthorized(){
        return "error/unauthor";
    }
}

*自定义未授权页面 /error/unauthor.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>未授权</title>
</head>
<body>
<div>
    <h3 style="color: darkslategrey">未授权,请
        <a th:href="@{/toLogin}">切换用户</a>,或进行授权操作</h3>
</div>
</body>
</html>

3.根据数据库中用户权限 为用户授权

// 自定义UserRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行了 授权 ==》" + principals.getRealmNames().toString());

        // 增加授权 所有用户都赋予权限
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Subject subject = SecurityUtils.getSubject();
        User principal = (User) subject.getPrincipal();

        // 通过数据库字段 增加 权限
        info.addStringPermission(principal.getPerms());

        return info;
    }

    // token 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了 认证 ==》" + token.getPrincipal().toString());

        // 数据库取数据 进行认证
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        // 前端传递 用户名 查询 数据库
        User user = userService.queryUserByName(userToken.getUsername());

        if (null == user){
            // 前端传递 与 数据库查询用户名 不一致
            return null; // 抛出异常 未知用户名
        }

        // 密码认证 shiro做 登录成功后将用户信息 传递给授权 
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

4.增加前端显示用户信息 与 注销功能

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
</head>
<body>
<div>
  <h1>首页</h1>
    <!---->
  <p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

    <!--用户信息-->
    <p th:text="${user_name}"
       th:if="${not #strings.isEmpty(user_name)}"></p>
</div>
<div>
  <a th:href="@{/user/add}">增加</a>
  <a th:href="@{/user/update}">修改</a>
  <a th:href="@{/show}">展示</a>
  <a th:href="@{/loginout.do}">注销</a>
</div>

</body>
</html>
    @RequestMapping("/loginout.do")
    public String loginOut(){
        Subject subject = SecurityUtils.getSubject();
        // 注销
        subject.logout();
        return "login";
    }
   user.login(token); // 调用自定义 UserRealm 认证方法
   model.addAttribute("user_name",token.getUsername());
   return "index";

三、拓展整合Thymeleaf引擎

<!--头引用-->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
shiro:hasPermission="xxx" 拥有某权限
shiro:guest="true" 未登录
shiro:authenticated="true" 已登陆
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
</head>
<body>
<div>
  <h1>首页</h1>
    <!---->
  <p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

    <!--用户信息-->
    <p th:text="${user_name}"
       th:if="${not #strings.isEmpty(user_name)}"></p>
</div>
<div>
  <!--整合 shiro 根据权限显示-->
  <div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">增加</a>
  </div>
  <div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">修改</a>
  </div>
  <a th:href="@{/show}">展示</a>
  <!--游客显示登录按钮 登录成功不显示-->
  <div shiro:guest="true">
    <a th:href="@{/toLogin}">登录</a>
  </div>
  <div shiro:authenticated>
    <a th:href="@{/loginout.do}">注销</a>
  </div>
</div>

</body>
</html>

~体验到Shiro框架的便捷之处,告别过滤器

上一篇下一篇

猜你喜欢

热点阅读