Spring Boot -- Apache Shiro

1. pom.xml

shiro并没有提供对应的Starter,而是使用的shiro-spring,其它的依赖都是辅助

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

<!-- thymeleaf -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- spring-data-jpa -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <scope>runtime</scope>
</dependency>

2. application.properties

#数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_test?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#jpa配置
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.show-sql=true
logging.level.com.example.demo.jpa.repository=debug

3. entity

用户-角色-权限
一个用户可以拥有多个角色,同一个角色也可以赋给多个用户,所以是用户和角色属于多对多关系,
一个角色可以有多种权限,一种权限属于多个角色,角色和权限也属于多对多关系,多对多需要中间表,多对多使用@ManyToMany标注,中间表使用@JoinTable来标注,使用joinColumns来指定连接列的名称,使用inverseJoinColumns来指定被连接的列的名称

用户表

@Entity
public class UserInfo {

    @Id
    @GeneratedValue
    private Integer uid;

    @Column(unique = true)
    private String username;
    private String password;
    private String name;
    //用于加密的字符串,保证密码生成的MD5的唯一性
    private String salt;
    //0:未认证,1:正常状态,2:用户被锁定
    private byte state;

    //立即从数据库中进行加载数据
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "uid")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
    private List<SysRole> sysRoles;
    //get/set
}

角色表

@Entity
public class SysRole {

    @Id
    @GeneratedValue
    private Integer id;
    private String role;
    private String description;
    private Boolean available = Boolean.FALSE;

    @ManyToMany
    @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "uid")})
    private List<UserInfo> userInfos;

    @ManyToMany
    @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "permissionId")})
    private List<SysPermission> sysPermissions;

    //get/set
}

权限表

@Entity
public class SysPermission {

    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    //权限类型,菜单/按钮
    @Column(columnDefinition = "enum('menu','button')")
    private String resourceType;
    private String url;
    //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
    private String permission;
    private Integer parentId;
    private String parentIds;
    private Boolean available = Boolean.FALSE;

    @ManyToMany
    @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "permissionId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
    private List<SysRole> sysRoles;

    //get/set
}

运行应用程序,通过jpa会自动生成表,表名和列名是按照驼峰转下划线的风格

  • 用户表: user_info,
  • 角色表: sys_role,
  • 权限表: sys_permission,
  • 用户角色中间表: sys_user_role,
  • 角色权限中间表: sys_role_permission
    在这里插入图片描述

4. 插入测试数据

插入一个admin用户,密码123456
插入三个角色:管理员、VIP会员、test
插入三个权限(资源): 每个资源包含 资源类型(菜单或按钮)、权限标识符(一般是模块:操作这种格式)、url地址
角色-权限:管理员角色中有用户管理、用户添加、用户删除三个权限
用户-角色:admin用户拥有管理员角色,有用户管理、用户添加二个权限,用户删除权限没有

INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);

INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP会员','vip');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');

INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用户管理',0,'0/','userInfo:view','menu','userInfo/userList');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用户添加',1,'0/1','userInfo:add','button','userInfo/userAdd');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用户删除',1,'0/1','userInfo:del','button','userInfo/userDel');

INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);

INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);

5.dao/service

@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserInfoDao userInfoDao;

    @Override
    public UserInfo findByUserName(String username) {
        return userInfoDao.findByUserName(username);
    }
}

6.ShiroConfig

Config

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //设置登录的路径,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");

        //设置登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");

        //设置访问没有权限跳转到的界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        //过滤器链,拦截的顺序是按照配置的顺序来的
        //过滤链定义,从上向下顺序执行
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //配置不会被拦截的路径,一般静态资源都不需要拦截,anon代表匿名的不需要拦截的资源,这里的静态资源的匹配模式配置成/static/**,
        filterChainDefinitionMap.put("/static/**", "anon");
        //登出路径使用logout拦截器
        filterChainDefinitionMap.put("/logout", "logout");
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    //凭证匹配器
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数md5(md5(""))
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    @Bean
    public AuthorizingRealm authorizingRealm(){
        AuthorizingRealm authorizingRealm = new DemoShiroRealm();
        authorizingRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return authorizingRealm;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(authorizingRealm());
        return securityManager;
    }

    /**
     * 开启shiro aop注解支持
     * 使用代理方式;所以需要开启代码支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        //数据库异常处理
        mappings.setProperty("DatabaseException", "databaseError");
        mappings.setProperty("UnauthorizedException","403");
        simpleMappingExceptionResolver.setExceptionMappings(mappings);
        simpleMappingExceptionResolver.setDefaultErrorView("error");
        simpleMappingExceptionResolver.setExceptionAttribute("ex");
        return simpleMappingExceptionResolver;
    }

}

AuthorizingRealm

public class DemoShiroRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    /**
     * 授权:SimpleAuthorizationInfo用于存储用户的所有角色(Set<String> roles)和所有权限(Set<String> stringPermissions)信息
     * 当执行某个方法时,方法上会有权限注解,例如@RequiresPermissions("userInfo:add"),
     * 此时就会去找AuthorizationInfo中的stringPermissions是否包含userInfo:add,如果包含就继续处理,
     * 如果不包含则跳转到shiro配置的为授权的地址
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();

        for (SysRole role : userInfo.getSysRoles()){
            //添加角色
            authorizationInfo.addRole(role.getRole());
            for (SysPermission permission : role.getSysPermissions()){
                //添加权限
                authorizationInfo.addStringPermission(permission.getPermission());
            }
        }

        return authorizationInfo;
    }

    /**
     * 认证
     * 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确
     * 当用户登录时会执行
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userService.findByUserName(username);

        if(userInfo == null){
            return null;
        }

        //userInfo.getCredentialsSalt()盐的生成方式 这里使用salt=username+salt
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getCredentialsSalt()), getName());
        return authenticationInfo;
    }
}

7.controller

HomeController

@Controller
public class HomeController {

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

    /**
     * 登录时先执行Realm中的认证方法,然后再执行登录方法
     * @param request
     * @return
     */
    @RequestMapping("/login")
    public String login(HttpServletRequest request){
        //登录失败从request中获取shiro处理的异常信息
        //shiroLoginFailure:就是shiro异常类的全类名
        String exception = (String) request.getAttribute("shiroLoginFailure");
        String msg = "";

        if(exception != null){
            if(UnknownAccountException.class.getName().equals(exception)){
                System.out.println("UnknownAccountException -- > 账号不存在:");
                msg = "UnknownAccountException -- > 账号不存在:";
            } else if (IncorrectCredentialsException.class.getName().equals(exception)){
                System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                msg = "IncorrectCredentialsException -- > 密码不正确:";
            } else if ("kaptchaValidateFailed".equals(exception)){
                System.out.println("kaptchaValidateFailed -- > 验证码错误");
                msg = "kaptchaValidateFailed -- > 验证码错误";
            }else {
                msg = "else >> "+exception;
                System.out.println("else -- >" + exception);
            }
        }
        request.setAttribute("msg", msg);
        // 此方法不处理登录成功,由shiro进行处理, 应为会在shiro中配置登录成功需要跳转的界面
        return "/login";
    }

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

}

UserInfoController

@RequestMapping("/userInfo")
@Controller
public class UserInfoController {

    /**
     * 用户查询
     * 查看用户信息的权限
     * @return
     */
    @RequiresPermissions("userInfo:view")
    @RequestMapping("/userList")
    public String userInfo(){
        return "userInfo";
    }

    /**
     * 用户添加
     * 添加用户的权限
     * @return
     */
    @RequiresPermissions("userInfo:add")
    @RequestMapping("/userAdd")
    public String userInfoAdd(){
        return "userInfoAdd";
    }

    /**
     * 用户删除
     * 删除用户的权限
     * @return
     */
    @RequiresPermissions("userInfo:del")
    @RequestMapping("/userDel")
    public String userInfoDel(){
        return "userInfoDel";
    }

}

转载
Spring Boot入门教程(二十五): Apache Shiro

猜你喜欢

转载自blog.csdn.net/lolwsyzc/article/details/83143689