• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

SSM整合Shiro实现认证、加密和授权

Shiro winrains 来源:IT播种机 1年前 (2019-08-31) 199次浏览

学习目标

1、shiro介绍
2、SSM整合Shiro

学习内容

1.shiro介绍

1.1介绍

  • shiro是一个java的 安全框架,apache的一个开源框架。简单易用。
  • 实现的功能:认证、授权、会话管理和密码加密等功能。
  • 可以用在java SE环境中,java EE环境中。

1.2shrio功能特点:

SSM整合Shiro实现认证、加密和授权

Authentication:认证
Authorization:授权
Session Management:session会话管理
Cryptography:密码加密
Caching:缓存
web Support:web支持
Concurrency:多线程、并发
Testing:测试
Run as: 以 某种 身份 运行
Remember Me:记住我

1.3shiro运行原理

SSM整合Shiro实现认证、加密和授权

可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:
Subject:主体,抽象的身份:当前的用户,网络爬虫、第三方的程序、进程
SecurityManager:安全管理器,管理所有的subjects;委托给SecurityManager进行安全管理。门面模式:Facade;需求—-》委托人————分发下去;
Realm:域,数据域。获取数据库中用户信息、权限信息。
接下来我们来从Shiro内部来看下Shiro的架构,如下图所示:

SSM整合Shiro实现认证、加密和授权

1.4过滤器

Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:
解释:

  • /admins/**=anon # 表示该 uri 可以匿名访问
  • /admins/**=auth # 表示该 uri 需要认证才能访问
  • /admins/**=authcBasic # 表示该 uri 需要 httpBasic 认证
  • /admins/*=perms[user:add:] # 表示该 uri 需要认证用户拥有 user:add:* 权限才能访问
  • /admins/**=port[8081] # 表示该 uri 需要使用 8081 端口
  • /admins/=rest[user] # 相当于 /admins/=perms[user:method],其中,method 表示 get、post、delete 等
  • /admins/**=roles[admin] # 表示该 uri 需要认证用户拥有 admin 角色才能访问
  • /admins/**=ssl # 表示该 uri 需要使用 https 协议
  • /admins/**=user # 表示该 uri 需要认证或通过记住我认证才能访问
  • /logout=logout # 表示注销,可以当作固定配置

注意:

  • anon,authcBasic,auchc,user 是认证过滤器。
  • perms,roles,ssl,rest,port 是授权过滤器。

2、shiro的hello world

参考官网的示例

public static void main(String[] args) {
    // 初始化工厂实例
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    // 实例化一个安全管理器
    SecurityManager securityManager = factory.getInstance();
    // 设置安全管理器
    SecurityUtils.setSecurityManager(securityManager);
    // 获取Subject:当前用户
    Subject currentUser = SecurityUtils.getSubject();
    // 从session中查询中当前登录的用户信息 ,类似于登录时从session中获取登录信息
    Session session = currentUser.getSession();
    session.setAttribute("someKey", "aValue");
    String value = (String) session.getAttribute("someKey");
    if (value.equals("aValue")) {
        log.info("Retrieved the correct value! [" + value + "]");
    }
    // let's login the current user so we can check against roles and
    // permissions:
    // 如果当前用户还没有认证
    if (!currentUser.isAuthenticated()) {
        // 身份令牌:用户名和密码 ;实际从前台登录页面获取
        UsernamePasswordToken token = new UsernamePasswordToken("root", "secret");
        // 记住我
        token.setRememberMe(true);
        try {
            // 模拟登录 :会将身份令牌信息传给安全管理器,委托给安全管理器进行认证
            currentUser.login(token);
        } catch (UnknownAccountException uae) { // 账号不存在
            log.info("没有该账号 " + token.getPrincipal());
        } catch (IncorrectCredentialsException ice) { // 密码不匹配
            log.info("Password for account " + token.getPrincipal() + " was incorrect!");
        } catch (LockedAccountException lae) {// 账号锁定
            log.info("The account for username " + token.getPrincipal() + " is locked. "
                    + "Please contact your administrator to unlock it.");
        }
        // ... catch more exceptions here (maybe custom ones specific to
        // your application?
        catch (AuthenticationException ae) { // 认证异常
            // unexpected condition? error?
        }
    }
    // say who they are:
    // print their identifying principal (in this case, a username):
    log.info("用户 [" + currentUser.getPrincipal() + "] 登录成功.");
    // 角色判断
    // test a role:
    if (currentUser.hasRole("schwartz")) {
        log.info("May the Schwartz be with you!");
    } else {
        log.info("Hello, mere mortal.");
    }
    // 权限判断
    // test a typed permission (not instance-level)
    if (currentUser.isPermitted("lightsaber:weild")) {
        log.info("You may use a lightsaber ring. Use it wisely.");
    } else {
        log.info("Sorry, lightsaber rings are for schwartz masters only.");
    }
    // a (very powerful) Instance Level permission:
    if (currentUser.isPermitted("winnebago:drive:eagle5")) {
        log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. "
                + "Here are the keys - have fun!");
    } else {
        log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
    }
    // all done - log out!
    // 注销
    currentUser.logout();
    System.exit(0);
}

3、SSM整合+shiro认证

步骤:

1、添加依赖

<!--shiro整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>

2、配置web.xml中的shiro的过滤器

<!--shiro的过滤器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3、配置shiro.xml文件

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--sessionManager -->
<!-- <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!–<property name="sessionDAO" ref="sessionDao"/>–>
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="simpleCookie"/>
</bean>
<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg name="name" value="shiro.sesssion"/>
<property name="path" value="/"/>
</bean>-->
<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--<property name="cacheManager" ref="cacheManager"/>-->
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<!--<property name="sessionManager" ref="sessionManager"/>-->
<property name="sessionMode" value="native"/>
<property name="realm" ref="myRealm"/>
</bean>
<!--缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!--数据域Realm:自己实现-->
<bean id="myRealm" class="com.aaa.realm.MyRealm">
<property name="name" value="myRealm"/>
<!--密码匹配器-->
<!-- <property name="credentialsMatcher">
<!– The 'bootstrapDataPopulator' Sha256 hashes the password
(using the username as the salt) then base64 encodes it: –>
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1024"/>
<!– true means hex encoded, false means base64 encoded –>
<!–<property name="storedCredentialsHexEncoded" value="false"/>–>
</bean>
</property>-->
</bean>
<!--生命周期管理-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--shiro过滤器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--注入安全管理器-->
<property name="securityManager" ref="securityManager"/>
<!--绑定登录页面-->
<property name="loginUrl" value="/login.jsp"/>
<!--绑定登录成功的后的页面-->
<property name="successUrl" value="/admin/index.jsp"/>
<!--未授权的页面-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean
defined will be automatically acquired and available via its beanName in chain
definitions, but you can perform overrides or parent/child consolidated configuration
here if you like: -->
<!-- <property name="filters">
<util:map>
<entry key="aName" value-ref="someFilterPojo"/>
</util:map>
</property> -->
<property name="filterChainDefinitions">
<value>
/index.jsp=anon
/user/login=anon
/user/logout=logout
/static/**=anon
/admin/**= authc
</value>
</property>
</bean>
</beans>

登录认证:

4、设置控制器中的登录方法:

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login(User user) {
        System.out.println("登录:" + user);
        // 身份令牌
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        Subject currentUser = SecurityUtils.getSubject();
        token.setRememberMe(true);
        try {
            currentUser.login(token);
        } catch (UnknownAccountException e) {
            throw new UnknownAccountException(e.getMessage());
        } catch (IncorrectCredentialsException e) {
            throw new IncorrectCredentialsException(e.getMessage());
        } catch (AuthenticationException e) {
            throw new AuthenticationException(e.getMessage());
        }
        return "admin/index";
    }
}
​

5、创建Realm类

public class MyRealm extends AuthenticatingRealm {
    // 实现用户的身份认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        System.out.println("开始身份认证:");
        // 获取用户名 :从登录页面传递过来的用户名
        // authenticationToken.getPrincipal();
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();// 获取用户名
        // 查询数据是否存在用户名
        System.out.println("用户名:" + username);
        // 根据用户名:账号;从数据中查询该用户名对应的密码
        String password = "abcdef";
        Object principal = username;
        Object credentials = password;
        String realmName = getName();
        // 存的从数据库取出来的用户名字 和密码
        AuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
        return info;
    }
}

密码加密

常见的 对称加密 算法主要有 DES、3DES、AES 等,常见的 非对称算法 主要有 RSA、DSA 等,散列算法 主要有 SHA-1、MD5 等。
使用散列算法:

 hashAlgorithmName:加密的算法名称 MD5(不可逆的)
 credentials:密码
 salt:盐(用户名+随机数字)
 hashIterations:迭代次数
 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations)

realm中的认证:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
        throws AuthenticationException {
    System.out.println("开始身份认证:");
    // 获取用户名 :从登录页面传递过来的用户名
    // authenticationToken.getPrincipal();
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    String username = token.getUsername();// 获取用户名
    // 查询数据是否存在用户名
    System.out.println("用户名:" + username);
    // 根据用户名:账号;从数据中查询该用户名对应的密码
    // 模拟从数据库中取出加密后的密码
    Object credentials = "f7b5cd56c1e2d49e5b69c9dde7679ca4";
    if ("test".equals(username)) {
        // 123456
        credentials = "4292bb58be34c59d28a0dcbd11932d49";
    } else if ("admin".equals(username)) {
        // abcdef
        credentials = "f7b5cd56c1e2d49e5b69c9dde7679ca4";
    }
    Object principal = username;
    // Object credentials=password;
    String realmName = getName();
    // 盐 :以用户名充当盐值
    ByteSource credentialsSalt = ByteSource.Util.bytes(username);
    // 从数据库取出来的 用户名字 和 加密后的密码
    AuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
    return info;
}

授权

不同的用户—》角色—-》权限
数据表:用户表、角色表、菜单表,用户角色关联表,角色菜单关联表;

 A角色 A资源(表、实体)) create
 update
 delete
 query
 资源:动作

shiro支持3种方式的授权验证:
1、编码
验证角色

//get the current Subject
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("administrator")) {
    //show a special button
} else {
    //don’t show the button
}

验证权限

Subject currentUser = SecurityUtils.getSubject();
Permission printPermission = new PrinterPermission("laserjet3000n","print");
If (currentUser.isPermitted(printPermission)) {
    //do one thing (show the print button?)•
} else {
    //don’t show the button?
}
String perm = "printer:print:laserjet4400n";
if(currentUser.isPermitted(perm)){
    //show the print button?
} else {
    //don’t show the button?
}

2、注解

//Will throw an AuthorizationException if none
//of the caller’s roles imply the Account
//'create' permission
@RequiresPermissions("account:create")•
public void openAccount( Account acct ) {
    //create the account
}
//Throws an AuthorizationException if the caller
//doesn’t have the ‘teller’ role:
@RequiresRoles( "teller" )
public void openAccount( Account acct ) {
    //do something in here that only a teller
    //should do
}

需要开启注解配置:

<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>

3、标签

<%@ taglib prefix="shiro" uri=http://shiro.apache.org/tags %>
<html>
<body>
<shiro:hasPermission name="users:manage">
<a href="manageUsers.jsp">
Click here to manage users
</a>
</shiro:hasPermission>
<shiro:lacksPermission name="users:manage">
No user management for you!
</shiro:lacksPermission>
</body>
</html>

修改MyRealm类:

// 实现授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    Object username = principals.getPrimaryPrincipal();
    // 根据用户名获取对应的角色,模拟从数据库中获取角色列表
    Set<String> roles = new HashSet<>();
    roles.add("admin");
    roles.add("guest");
    roles.add("test");
    // 返回的对应的 AuthorizationInfo的实现类的对象
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    // 绑定角色列表
    info.setRoles(roles);
    // 绑定权限列表
    // info.setStringPermissions();
    return info;
}

在相应的控制器中增加注解方式的角色或者权限判断,根据返回的异常进行处理。

作者:IT播种机

来源:https://www.toutiao.com/a6704917630479761924/


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (2)