shiro用户加密默认方式_Shiro 基本操作

Shiro介绍

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。毕竟官网有十分钟入门,足矣可见该框架的上手难度。

Shiro 功能介绍

gongneng.png

从上图可看出来,主要功能有4个,以及支持一些其他特性。

主要功能

Authentication:认证,用于登录

Authorization:授权,判断用户是否拥有资源权限

Session Management:会话,登录后就是一次会话。

Cryptography:加密,用户密码密文存入数据库。

支持的功能

Web Support:支持Web,SE也能用

Caching:缓存

Concurrency:支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去

Testing:提供测试支持

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问

Remember Me:记住我,

PS: Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro

在使用的时候还有一个很重要的对象叫:Subject ,该对象表示当前访问的用户。

Shiro的一些配置

自定义Realm,需继承AuthorizingRealm。用户认证与授权。

把Realm交给SecurityManager。统一管理。

配置 ShiroFilterFactoryBean 把需要拦截的地址和SecurityManager

第一步:写一个Realm,来认证。

AuthorizingRealm 该接口需要重写两个方法。

doGetAuthorizationInfo 授权,访问某一个具体资源判断是否有该权限

doGetAuthenticationInfo 认证,登录认证

public class UserRealm extends AuthorizingRealm {

@Resource

private UserInfoService userInfoService;

@Resource

private MenuMapper menuMapper;

/**

* 认证,你需要通过用户名查数据库,在进行密码对比(内部自动对比,错误会抛异常)

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

// token 是在Controller层传入进来的

// getPrincipal 内部代码调用的就是 getUsername()方法

String username = (String)token.getPrincipal();

// 通过用户名查询该用户信息

UserInfo user = userInfoService.login(username);

if(user != null){

// 第一个参数存用户信息。这里存的,其他地方可以取出来。存什么,则取什么。

// 第二个参数查询出来的密码,内部会判断该密码和输入密码是否相同

// 第三个参数自定义Realm的信息

return new SimpleAuthenticationInfo(user, user.getPwd(), getName());

}

return null;

}

/**

* 授权,写了一个授权判断的时候会自动执行以下代码。

* 注意如果授权你采用注解的方式,必须在配置中添加两个Bean,否则不会进入该方法判断。

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

// 该方法就是取的认证时,存的第一个参数。

UserInfo user = (UserInfo) principals.getPrimaryPrincipal();

if(user == null){

return null;

}

List

for (Menu m: menus) {

info.addStringPermission(m.getUrl());

}

return info;

}

}

第二部写一个配置类把自定义Realm交给SecurityManager

// 申明配置类

@Configuration

public class ShiroConfig {

// 把自定义存入ioc容器中

@Bean

public UserRealm initRealm() {

return new UserRealm();

}

@Bean

public SecurityManager initSecurityManager() {

DefaultWebSecurityManager manager = new DefaultWebSecurityManager();

// 将自定义Realm注入进去

manager.setRealm(initRealm());

// 这个是记住我功能

manager.setRememberMeManager(cookieRememberMeManager());

return manager;

}

@Bean

public CookieRememberMeManager cookieRememberMeManager() {

//实例化rememberme管理器

CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();

//定义Cookie cookie的名字为rememberMe

SimpleCookie cookie = new SimpleCookie("rememberMe");

//定义Cookie的有效时间(s)

cookie.setMaxAge(24 * 60 * 60 * 3);

//将cookie设置到rememberme管理器中

cookieRememberMeManager.setCookie(cookie);

//设置cookie的值的加密密钥(设置用户数据序列化以后采用的加密密钥)

cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));

return cookieRememberMeManager;

}

/**

* 该方法用于过滤请求

*/

@Bean

public ShiroFilterFactoryBean shiroFilter() throws UnsupportedEncodingException {

//实例化Filter工厂

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//注册securityManager

shiroFilterFactoryBean.setSecurityManager(initSecurityManager());

//设置Shiro过滤器过滤规则

//LinkHashMap是有序的,shiro会根据添加的顺序进行拦截,匹配到过滤器后就执行该过滤器不会在继续向下查找过滤器

Map filterChainDefinitionMap = new LinkedHashMap();

/*

* anon:所有的url都可以不登陆的情况下访问

* authc:所有url都必须 认证 通过才可以访问

* user:有记住我才能访问

* role:拥有某个角色才能访问 filterChainDefinitionMap.put("/user/add", "perms[user:add]");

* perms:拥有某个资源的权限才能访问

*/

filterChainDefinitionMap.put("/js/**", "anon");

filterChainDefinitionMap.put("/css/**", "anon");

filterChainDefinitionMap.put("/login.html", "anon");

filterChainDefinitionMap.put("/login", "anon");

filterChainDefinitionMap.put("/logout", "logout");

filterChainDefinitionMap.put("/**", "user");

//未登录时重定向的网页地址

shiroFilterFactoryBean.setLoginUrl("/login.html");

// 未授权的时候跳转地址

shiroFilterFactoryBean.setUnauthorizedUrl("/login.html");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

return shiroFilterFactoryBean;

}

// 这下面两个 Bean 用于开启注解形式的授权验证。(就是开启AOP)

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {

AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();

advisor.setSecurityManager(initSecurityManager());

return advisor;

}

@Bean

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){

DefaultAdvisorAutoProxyCreator app=new DefaultAdvisorAutoProxyCreator();

app.setProxyTargetClass(true);

return app;

}

}

这个配置的意思是:放行静态资源,登录页面,和登录请求,其他全部请求都被拦截需要对应的权限。

以上就是Shiro 相关的配置就算是写完了。但是是不够的,因为没有调用上面的方法。

@RequestMapping("/login")

public Result login(String username,String pwd,boolean rememberme){

// 创建一个token,这里的token 就是自定义Realm取出来的token

UsernamePasswordToken token = new UsernamePasswordToken(username, pwd, rememberme);

// subject 表示当前访问的用户

Subject subject = SecurityUtils.getSubject();

// 判断是否认证过 认证过为true,没认证为false

if (!subject.isAuthenticated()){

// 执行登录方法,该方法不是Shiro提供的,但是最终会执行自定义Realm的认证方法上面。

subject.login(token);

}

return new Result("200","ok",null,null);

}

这样一个认证的流程就算走完了。

自定义Realm

编写配置类,把Realm注入IOC容器,并且放进SecurityManager

编写请求过滤,并把SecurityManager放入ShiroFilterFactoryBean对象中

在登录方法中,把用户名、密码和记住我封装进UsernamePasswordToken对象中。

使用SecurityUtils获取Subject。

通过Subject.login(token)进行验证,验证成功则正常执行,验证失败则抛异常。

代码逻辑对比

在没有Shiro的时候代码逻辑是这样的

Controller接受用户名密码,调用Service,查询dao层,结果返回给Service层,Service层进行密码对比并把结果返回给Controller。

有了Shiro是这样的:

Controller接受用户名密码,对用户名密码进行一次封装,接着调用subject的login方法。

login方法,会去自动找到写的Realm(前提继承了AuthorizingRealm),在Realm调用Service,dao层查询,结果层层返回,返回到Realm,对结果集进行再次封装。封装的里面他会自动进行密码的对比。失败则抛异常,成功则返回到Controller层。

图形化说明:

[图片上传失败...(image-a93460-1593264897452)]

授权方式

编码方式授权判断:

Subject subject = SecurityUtils.getSubject();

if (subject.hasRole("administrator")) {

//拥有角色administrator

} else {

//没有角色处理

}

if(subject.isPermitted("insert")){

// 拥有某权限

}else{

// 没有权限

}

注解方式:(配置文件需要额外配置,上面有说明)

注解

意义

案例

@RequiresAuthentication

验证用户是否登录

@RequiresUser

当前用户已经验证过了或则记住我了

@RequiresGuest

是否是游客身份

@RequiresRoles

拥有该角色

@RequiresRoles({“admin”})

@RequiresPermissions

需要拥有权限

@RequiresPermissions("/development/bug/update")

密码加密

上面的例子当中并没有涉及到密码加密,但是流程都是一毛一样的,唯一区别就是密码不同。

new SimpleHash(algorithmName, source, salt, hashIterations) 用来加密

new SimpleHash("MD5", "123456", "abcd")

四个参数:

algorithmName 加密方式写 MD5

source 需加密的密码

salt 加密的盐

hashIterations 加密几次

在自定义Realm里面同样调用 new SimpleAuthenticationInfo方法,只不过参数变成了4个

第一个参数存用户信息。这里存的,其他地方可以取出来。存什么,则取什么

第二个参数查询出来的密码,内部会判断该密码和输入密码是否相同

第三个参数 加密盐,需要通过 ByteSource.Util.bytes(solt)

第四个参数自定义Realm的信息

but !!! 虽然你这样写了。但是!他内部是不会加密滴!

需要配置文件中加入这么一个东东:

@Bean

public HashedCredentialsMatcher hashedCredentialsMatcher() {

HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

// 散列算法, 与注册时使用的散列算法相同

hashedCredentialsMatcher.setHashAlgorithmName("MD5");

// 散列次数, 与注册时使用的散列册数相同

hashedCredentialsMatcher.setHashIterations(1);

// 生成16进制 默认生成的32位

//hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);

return hashedCredentialsMatcher;

}

// 这么做了还差一点点,要把这个Bean注入 自定义Realm中

@Bean

public UserRealm initRealm() {

UserRealm userRealm = new UserRealm();

// 注入进去它才会使用,这下就没问题了

userRealm.setCredentialsMatcher(hashedCredentialsMatcher());

return userRealm;

}

最后补充一点

你会发现一个问题,那就是每次授权的时候他都会走查询,意味着每次都要查询数据库,如果访问量大了明显很难受的,所以可以加缓存!

使用Ehcache(系统混合缓存方案);

使用本地内存缓存方案;

自定义CacheManager(比如Redis用来作为缓存)

这里就短暂介绍一下最简单的(方案二)。

很简单,就算配置成Java代码也是分分钟的事情,这样只会第一次走查询,后面都走缓存了

但是还是有缺点:我在你登录期间修改了你的权限,并不能动态修改。他依旧走的是缓存。

所以其他方案以后在整一整。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值