菜单

springboot 单体架构的shiro集成

2018年11月15日 - jQuery

这边以的凡eclipse 开发工具

3.位证明

以证明、授权内部贯彻机制中都出提到,最终处理还以付诸Real进行拍卖。因为于Shiro中,最终是经Realm来获得应用程序中之用户、角色跟权限信息之。通常情况下,在Realm中会一直由咱的多少源中获取Shiro需要的征信息。可以说,Realm是专用于安框架的DAO.

1.springboot 版本是2.0的,引入了2个shiro 的依赖,如下

证实现

Shiro的认证过程最终见面暨由Realm推行,这时会调用RealmgetAuthenticationInfo(token)方法。
拖欠法要实施以下操作:
1、检查提交的拓展求证的令牌信息
2、根据令牌信息由数据源(通常也数据库)中赢得用户信息
3、对用户信息进行匹配验证。
4、验证通过将回来一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException雅信息。
使当我们的应用程序中若做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写得用户信息之主意。

既要进行身份权限控制,那么少不了创建用户实体类,权限实体类。

每当权力管理体系受,有这样几个角色非常要紧,这个要不亮堂的话,那么就是异常不便掌握,我们为何这么编码了。
第一是用户表:在用户表中保存了用户之中坚信息,账号、密码、姓名,性别等;
第二是:权限表(资源+控制权限):这个表中主要是保留了用户的URL地址,权限信息;
其三哪怕是角色表:在这发明要保存了系有的角色;
季即是关联表:用户-角色管理表(用户在系面临都有啊角色,比如admin,vip等),
第五即使是角色-权限关联表(每个角色还发生啊权限可以进行操作)。依据这理论,我们开展来开展编码,很鲜明的我们第一步就是是要开展实体类的创立。在此地我们用Mysql和JPA进行操作数据库。

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<!-- 安全框架:密码加密、权限控制、身份验证 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2-RELEASE</version>
</dependency>

引入mysql和JPA的赖。JPA版本(Mar 03, 2017),mysql版本默认,可以协调选择版本

<!-- Spirng data JPA依赖; -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
<!-- mysql驱动; -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

 

application.yml配置mysql数据库暨JPA

spring:
    datasource:
      url: jdbc:mysql://localhost:3306/数据库名称
      username: mysql的登录帐号
      password: mysql的登录密码
      driver-class-name: com.mysql.jdbc.Driver
    jpa:
      database: mysql
      show-sql: true
      hibernate:
        ddl-auto: update
        naming:
          strategy: org.hibernate.cfg.DefaultComponentSafeNamingStrategy
      properties:
         hibernate:
            dialect: org.hibernate.dialect.MySQL5Dialect

JPA强大的处当受得活动建表,只要在实体类中因故好注解
UserInfoSysRoleSysPermission关于前面的关联表我们下JPA进行自动生成。

UserInfo用户信息实体类
@Entity宣示也实体类
@Id``@GeneratedValue说Id是独自增主键,映射到公是近乎吃的Integer
uid
@Column(unique =true)是凭username这个字段的值在及时张表里不能够再次,所有记录值都如唯一,就如主键那样
@ManyToMany(fetch=FetchType.EAGER)切莫写默认为LAZY
如果是EAGER,那么表示取出即长达数据常常,它事关的数额也同时取出放入内存中.
如果是LAZY,那么取出即漫漫数常常,它事关的数据并无得到出来
表关联@JoinTable
name属性也总是两独说明的表名称。若不指定,则应用默认的表名称,格式:"表名1" + "_" + "表名2"(JPA会见也咱新建这个发明)
joinColumn性表示,在保留关系的表中,所保存关联关系之外键的字段,并配合@JoinColumn记使用;
inverseJoinColumn属性与joinColumn类,它保存之是保留关系的另外一个外键字段;

@Entity
public class UserInfo implements Serializable{
    @Id@GeneratedValue
    private Integer uid;
    @Column(unique =true)
    private String username;//帐号
    private String name;//名称(昵称或者真实姓名,不同系统不同定义)
    private String password; //密码;
    private String salt;//加密密码的盐
    private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
    @ManyToMany(fetch=FetchType.EAGER)//立即从数据库中进行加载数据;
    @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<SysRole> roleList;// 一个用户具有多个角色
    set和get方法....
    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt(){
        return this.username+this.salt;
    }
//重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
}

SysRole系角色实体类

@Entity
public class SysRole {
    @Id@GeneratedValue
    private Integer id; // 编号
    private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
    private String description; // 角色描述,UI界面显示使用
    private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户

    //角色 -- 权限关系:多对多关系;
    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
    private List<SysPermission> permissions;

    // 用户 - 角色关系定义;
    @ManyToMany
    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
    private List<UserInfo> userInfos;// 一个角色对应多个用户
    set和get方法....
}

SysPermission权实体类
@Column(columnDefinition="enum('menu','button')")``columnDefinition性表示创建表时,该字段创建的SQL语句,一般用于通过Entity生成表定义时采用。
例如
columnDefinition性能的独特使用:
编程语言中字符串一般还为此String意味着,但是数据库中varcahr数值类有长限制,一旦得大文本,则需要text数值类
但是String类型默认投的数值类是varcharcolumnDefinition足展开额外指定
@Column(name = "Remark",columnDefinition="text") private String remark;

  1. shiro
    主要分为2部分,第一片是shiro的部署类似,我在src/main/resources添加config文件夹,新建ShiroConfig。
这边枚举类型enum,resourceType只能是menu或者button,其他都异常
 @Column(columnDefinition="enum('menu','button')")
    private String resourceType;

具体的SysPermission实体类

@Entity
public class SysPermission implements Serializable{
    @Id@GeneratedValue
    private Integer id;//主键.
    private String name;//名称.

    @Column(columnDefinition="enum('menu','button')")
    private String resourceType;//资源类型,[menu|button]
    private String url;//资源路径.
    private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
    private Long parentId; //父编号
    private String parentIds; //父编号列表
    private Boolean available = Boolean.FALSE;

    @ManyToMany
    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List<SysRole> roles;
    set和get方法....
}

顶此地实体类就编码完毕了,在这里我们看到底是3只实体类:UserInfo,SysRole,SysPermission
相应的是数据库的五张表:
UserInfo
SysUserRole
SysRole
SysRolePermission
SysPermission
(只需要跑一下主次就算会初步创建表)

创建完表之后如果输入下多少

INSERT INTO `sys_permission` VALUES ('1', '�', '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO `sys_permission` VALUES ('2', '�', '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO `sys_permission` VALUES ('3', '�', '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');
INSERT INTO `sys_role` VALUES ('1', '�', '管理员', 'admin');
INSERT INTO `sys_role` VALUES ('2', '�', 'VIP会员', 'vip');
INSERT INTO `sys_role_permission` VALUES ('1', '1');
INSERT INTO `sys_role_permission` VALUES ('1', '2');
INSERT INTO `sys_role_permission` VALUES ('1', '3');
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `user_info` VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', '0');

生一样首文章将出口实现身份证明,权限控制

  首先,shiro会配置外的拦截器 shiroFilter(由于swagger和druid的页面吗会叫拦截,这里而放行,这是独坑),

章主要参考于作者林祥纤的博客

http://412887952-qq-com.iteye.com/blog/2299777

  其次,会部署缓存管理方式,这里我利用的凡 shiro-redis
(是大神已经写好之redis的自动化管理,自己不怕无见面去写更多之代码了,比如序列化的题材,redis存储问题相当)

@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private int port;

// @Value("${spring.redis.timeout}") 
// private int timeout;


/**
* 修复Spring Boot整合shiro出现UnavailableSecurityManagerException 问题
* 此处设置相当于在web.xml中增加filter
*/
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}

/**
* SHIRO核心拦截器配置
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 拦截器.顺序判断
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// authc:所有url都必须认证通过才可以访问
// anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/user/login", "anon");
// 放行静态资源
filterChainDefinitionMap.put("/static/**", "anon");
// swagger2 放行
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
// druid 监控放行
filterChainDefinitionMap.put("/druid/**", "anon");
// 退出 过滤器
filterChainDefinitionMap.put("/user/logout", "logout");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

// 配置退出过滤器,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index.html");
// 未授权要跳转的链接;
shiroFilterFactoryBean.setUnauthorizedUrl("/login.html");

return shiroFilterFactoryBean;
}

/**
* securityManager 安全管理器 (通过 authorize 调用 自定义 realm 数据进行认证和授权)
*/
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myRealm);

// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());

// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}

/**
* 自定义realm; (账号密码校验;权限等)
* 
* @return
*/
@Bean(name = "myRealm")
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
// 添加 matcher加密算法到 MyRealm
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}

/**
* 密码匹配凭证管理器
*
* @return
*/
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 采用MD5方式加密
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 设置加密次数
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}

/**
* Shiro生命周期处理器
* 
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions 授权注解)
* 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}

/**
* 配置shiro redisManager 使用的是shiro-redis开源插件
* 
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
//redisManager.setTimeout(timeout);
// redisManager.setPassword(password);
return redisManager;
}

/**
* cacheManager 缓存 redis实现 使用的是shiro-redis开源插件
* 
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}

/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}

/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}


/*@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
// key序列化方式
template.setKeySerializer(redisSerializer);
// value序列化
template.setValueSerializer(redisSerializer);
// value hashmap序列化
template.setHashValueSerializer(redisSerializer);
// key haspmap序列化
template.setHashKeySerializer(redisSerializer);
//
return template;
}*/
}

3.shiro
的其它一个重中之重片段是realm的部署,其中蕴含2只地方,一凡是权力的自定义设定,二凡登入认证的自定义设定,我在src/main/resources添加realm文件夹,新建MyRealm。由于业务的原委,我这边仅仅开了登入的征,即加密算法MD5的征

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    /**
     * 在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
     * Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
     * 该方法主要执行以下操作:
     *
     * 检查提交的进行认证的令牌信息 根据令牌信息从数据源(通常为数据库)中获取用户信息 对用户信息进行匹配验证。
     * 验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
     * 验证失败则抛出AuthenticationException异常信息。而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    // 1.从主体传过来的认证信息中,获取用户名,密码
    String account = (String) token.getPrincipal();
    // String password = new String((char[]) token.getCredentials());

    // 2.通过用户名去到数据库中获取凭证
    AdminUser adminUser = this.userService.findUserByAccount(account);

    // 判断用户是否存在
    if (adminUser == null) {
        throw new UnknownAccountException();
    }
    // 判断用户是否有效
    if (adminUser.getAdminIsenable() == 0) {
        throw new LockedAccountException();
    }

    // 3.加密:MD5+salt,在这里添加盐值,以用户名作为盐值
    ByteSource salt = ByteSource.Util.bytes(adminUser.getAdminName());

    String realName = this.getName();

    // 4.验证凭证信息(密码信息)
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(account,
        adminUser.getAdminPassword(), salt, realName);

    // 5.当shiro验证成功,把用户信息放在session里
    Session session = SecurityUtils.getSubject().getSession(true); 
    session.setAttribute("activeUser", adminUser.getAdminAccount()); 

    return authenticationInfo;
    }

    /**
     * 授权用户权限 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * ,它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * ,如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     * 
     * shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
     * 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
     * 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole());
     * authorizationInfo.addStringPermission(p.getPermission());
     * 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles);
     * authorizationInfo.setStringPermissions(stringPermissions);
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”);
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问,
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”,
     * “roles[100002],perms[权限添加]”);
     * 就说明访问/add这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。
     * 
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // 1.从主体传过来的认证信息中,获取用户名
    // String acoount = (String) principals.getPrimaryPrincipal();
    // 查询用户名称
    // User user = userService.findUserByAccount(acoount);
    // 添加角色和权限
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    // for (Role role:user.getRoles()) {
    // //添加角色
    // simpleAuthorizationInfo.addRole(role.getRoleName());
    // for (Permission permission:role.getPermissions()) {
    // //添加权限
    // simpleAuthorizationInfo.addStringPermission(permission.getPermission());
    // }
    // }
    return simpleAuthorizationInfo;

    }
}

4.
最终是当controller中,怎么调用这个证,返回路是自家好包裹的一个项目,可以协调定义。这里需要小心的凡,要失去新建subject实例,才会去调动用login验证。

    public Result<?> login(@RequestParam(value = "account") String account,
        @RequestParam("password") String password) {
    // 结果信息
    Result<?> result = new Result<>();
    // 创建Subject实例
    Subject subject = SecurityUtils.getSubject();

    // 将用户名及密码封装到UsernamePasswordToken
    UsernamePasswordToken token = new UsernamePasswordToken(account, password);

    try {
        // 完成登录验证
        subject.login(token);
        // 判断当前用户是否验证成功
        if (subject.isAuthenticated() == true) {
        // 登入成功
        ResultEnum resultEnum = ResultEnum.AdminSuccess;
        result = ResultUtil.getSuccess(resultEnum.getCode(), resultEnum.getMsg(),"");
        // 打印到控制台
        LogUtil.printLog(result.getMsg()+",登入用户: " + account);
        }

    } catch (LockedAccountException e) {
        // 用户无效
        throw new MyRuntimeException(ResultEnum.LockedAccountException);
    } catch (IncorrectCredentialsException e) {
        // 密码不正确
        throw new MyRuntimeException(ResultEnum.IncorrectCredentialsException);
    } catch (AuthenticationException e) {
        // 用户不存在
        throw new MyRuntimeException(ResultEnum.UnknownAccountException);
    } catch (Exception e) {
        throw e;
    }
    return result;
    }

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图