引言

这里的demo涉及到的内容可能有点多。所以就放上了源码。在编写本文的时候我修改了一些注解可能和源码不一样,代码功能部分都是一样的,

源码下载:本地下载

教程

1.创建数据库

创建数据库记得导入数据,我这里的账号是benzhu,密码是加密加盐过后的5282d562ad1f43221a173ab92c1793d2(解密后是123456);角色是admin;权限是user:update。

2.导入Maven

aop包是注解授权需要导入的包。这里只给导入的包而已;自己创建Spring Boot。

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</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-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.2.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Spring Boot JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--shiro注解(aop生效) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

3.配置文件

application.properties

# 端口设置
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/shiro?&useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#开启aop
spring.aop.proxy-target-class=true

4.配置自定义Realm

realm/CustomRealm.java

package com.benzhu.shiro.realm;

import com.benzhu.shiro.dao.UserDao;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

@Component("realm")
public class CustomRealm extends AuthorizingRealm {

    @Resource
    private UserDao userDao;

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //重写授权方法
        String username = (String) principals.getPrimaryPrincipal();
        //从缓存中拿到角色数据(没有设置缓存只能再查一次数据库)
        Set<String> roles = getRolesByUserName(username);
        //从缓存中拿到权限数据(没有设置缓存只能再查一次数据库)
        Set<String> permissions = getPermissionUserName(roles);
        //返回对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }

    private Set<String> getPermissionUserName(Set<String> roles) {
        Set<String> sets = new HashSet<>();
        for (String role : roles){
            List<String> permission = userDao.getPermissionByUserName(role);
            for (String permis:permission) {
                //这里输出可以看出运行了两次
                System.out.println(permis);
                sets.add(permis);
            }
        }
        return sets;
    }

    private Set<String> getRolesByUserName(String username) {
        List<String> roles = userDao.getRolesByUSerName(username);
        Set<String> sets = new HashSet<>(roles);
        return sets;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //重写验证方法
        //1.从主体传过来的认证信息中获取用户名
        String username = (String) token.getPrincipal();
        //2.通过用户名到数据库中获取凭证
        String password = getPasswordByUserName(username);
        if(password == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,"customRealm");
        //加盐验证
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("benzhu"));
        return authenticationInfo;

    }

    private String getPasswordByUserName(String username) {
        //数据库查询数据
        return userDao.getUserPassWord(username);
    }

}

5.编写Shiro的配置类

filter/ShiroConfiguration.java

package com.benzhu.shiro.filter;

import com.benzhu.shiro.realm.CustomRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
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;

import javax.annotation.Resource;
import java.util.LinkedHashMap;

@Configuration
public class ShiroConfiguration {

    @Resource(name = "realm")
    private CustomRealm customRealm;

    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登录的url和登录成功的url以及验证失败的url
        bean.setLoginUrl("/login");
        bean.setUnauthorizedUrl("/error");
        //配置访问权限
        LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/sublogin", "anon");
        filterChainDefinitionMap.put("/usererror", "anon");
        filterChainDefinitionMap.put("/**","authc");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }



    //配置核心安全事务管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(customRealm);
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //创建加密对象
        matcher.setHashAlgorithmName("md5"); //加密的算法
        matcher.setHashIterations(1);//加密次数
        customRealm.setCredentialsMatcher(matcher); //放入自定义Realm
        return manager;
    }

    //以下是开启注解支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

6.编写用户实体类

domain/User.java

package com.benzhu.shiro.domain;

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

7.编写连接数据库的接口和实现类

dao/UserDao.java

package com.benzhu.shiro.dao;

import java.util.List;

public interface UserDao {
    String getUserPassWord(String username);

    List<String> getRolesByUSerName(String username);

    List<String> getPermissionByUserName(String username);
}

dao/UserDao.java

package com.benzhu.shiro.dao.impl;

import com.benzhu.shiro.dao.UserDao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Component
public class UserDaoImpl implements UserDao {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public String getUserPassWord(String username) {
        String sql = "select password from users where username = ?";
        List<String> list = jdbcTemplate.query(sql, new String[]{username}, new RowMapper<String>(){

            @Override
            public String mapRow(ResultSet resultSet, int i) throws SQLException {
                return resultSet.getString("password");
            }
        });

        if(list==null){
            return null;
        }
        return list.get(0);
    }

    @Override
    public List<String> getRolesByUSerName(String username) {
        String sql = "select role_name from user_roles where username = ?";
        List<String> list = jdbcTemplate.query(sql, new String[]{username}, new RowMapper<String>() {
            @Override
            public String mapRow(ResultSet resultSet, int i) throws SQLException {
                return resultSet.getString("role_name");
            }
        });
        return list;
    }

    @Override
    public List<String> getPermissionByUserName(String username) {
        String sql = "select permission from roles_permissions where role_name = ?";
        List<String> list = jdbcTemplate.query(sql, new String[]{username}, new RowMapper<String>() {
            @Override
            public String mapRow(ResultSet resultSet, int i) throws SQLException {
                return resultSet.getString("permission");
            }
        });
        return list;
    }
}

8.编写Controller

里面写了一些输出语句更好的理解Shiro的工作原理。

controller/UserController.java

package com.benzhu.shiro.controller;

import com.benzhu.shiro.domain.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller("UserController")
@RequestMapping("/")
public class UserController {

    @RequestMapping(value = "login")
    public String login(){
        return "login";
    }

    @RequestMapping(value = "usererror")
    public String error(){
        return "usererror";
    }

    @RequestMapping(value = "sublogin")
    public String sublogin(User user){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
        try {
            subject.login(token);
            try {
                subject.checkRole("admin");
                subject.checkPermission("user:update");
                    //理解非注解也可以做转跳拦截之类的动作
                    if(subject.hasRole("admin")){
                        System.out.println("拥有admin角色!");
                    }
                    if(subject.isPermitted("user:update")){
                        System.out.println("拥有user:update权限!");
                    }
                return "index";
            }catch (UnauthorizedException exception){
                System.out.println("角色授权或者权限授权失败!");
                return "error";
            }
        }catch (AuthenticationException e){
            System.out.println("认证失败!");
            return "usererror";
        }
    }

    @RequiresRoles("admin")
    @RequestMapping("testroels")
    @ResponseBody
    public String testroels(){
        return "有admin角色!";
    }

    @RequiresRoles("admin1")
    @RequestMapping("testroels1")
    @ResponseBody
    public String testroels1(){
        return "有admin1角色!";
    }

    @RequiresPermissions("user:update")
    @RequestMapping("testPermission")
    @ResponseBody
    public String testPermission(){
        return "拥有user:update权限";
    }

    @RequiresPermissions("user:update1")
    @RequestMapping("testPermission1")
    @ResponseBody
    public String testPermission1(){
        return "拥有user:update1权限";
    }

    @RequiresRoles("admin")
    @RequestMapping("exit")
    public String exit(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }
}

9.编写前端页面

前端页面比较多。

error.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>错误</title>
</head>
<body>
角色没有权限或者权限不足。<br>
<a href="/login">返回登陆</a>
</body>
</html>

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>首页</title>
</head>
<body>
恭喜你,登陆成功。<br>
<a href="/exit">退出</a>
</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="utf-8">
    <title>登陆</title>
</head>
<body>
    <form action="sublogin" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="登陆">
    </form>
</body>
</html>

usererror.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>错误</title>
</head>
<body>
账号或者密码错误。<br>
<a href="/login">返回登陆</a>
</body>
</html>

10.启动服务测试

  • 1.登陆账号benzhu密码123456。

  • 2.根据controller的路径来访问进行测试。