shiro整合到ssm

第一次学习shiro,记录下,欢迎指出存在的问题。至于里面的sql查询,只是单纯起到效果,有空再去优化数据结构与查询语句。

1.首先导入jar包

<!-- Spring 整合Shiro需要的依赖 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-aspectj</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-cas</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-guice</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-quartz</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro.tools</groupId>
      <artifactId>shiro-tools-hasher</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.3.2</version>
    </dependency>

2.在web.xml文件加上过滤器

<!--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.配置spring的配置文件

创建applicationContext-shiro.xml

(1)可以在web.xml配置这个加载spring配置文件时,加上通配符*

<!-- 1.启动spring 的容器 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext*.xml</param-value>
  </context-param>

(2)也可以不加通配符,在spring配置文件加上

<import resource="classpath:applicationContext-shiro.xml"/>

(3)applicationContext-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">

    <!--=============================配置shiro安全框架=====================================-->
    <!-- 配置緩存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- 指定 ehcache 的配置文件 -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <!-- 配置进行授权和认证的 Realm,要新增一个java类来实现,下面会有,class=包名.类名,init-methood是初始化的方法 -->
    <bean id="myRealm" class="com.liqiye.system.MyRealm" init-method="setCredentialMatcher"></bean>

    <!-- 配置 Shiro 的 SecurityManager Bean. -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="realm" ref="myRealm"/>
    </bean>

    <!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 配置 ShiroFilter bean: 该 bean 的 id 必须和 web.xml 文件中配置的 shiro filter 的 name 一致  -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 装配 securityManager -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 配置登陆页面 -->
        <property name="loginUrl" value="/view/loginPage"/>
        <!-- 登陆成功后的页面,如果不配置登录成功后跳转到上一次请求的url -->
        <property name="successUrl" value="/view/mainPage"/>
        <!--没有权限跳转的页面-->
        <property name="unauthorizedUrl" value="/view/noPeomission"/>
        <!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截.  -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 配置登出: 使用 logout 过滤器 -->
                /logout = logout
                <!--登录页面与登录请求拦截器为未登录-->
                /view/loginPage = anon
                /view/login = anon
                <!--/user.jsp = roles[user]
                /admin.jsp = roles[admin]-->
                /** = authc
            </value>
        </property>
    </bean>

    <!--设置全局异常捕获的跳转页面,是spring的配置,上面的unauthorizedUrl若是没生效就设置这个-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--这里配置的值不是url,是访问地址,springmvc配置的前后缀这里也生效-->
                <prop key="org.apache.shiro.authz.UnauthorizedException">/error/noPeomission</prop>
            </props>
        </property>
        <!--上面设置的异常都没捕获到,就轮到这个,这个捕获所有异常,跳转到默认错误页面-->
        <property name="defaultErrorView" value="/error/defaultError"/>
    </bean>

</beans>

4.配置springmvc的配置文件

在springmvc配置文件下面加上如下代码:

<!--shiro配置-->
<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>

5.加上shiro配置文件

上面的配置自己指定了shiro配置文件,在resource目录下新建一个文件ehcache-shiro.xml

这是官方提供的配置文件,需要修改参数可以自行去对应修改

<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one
  ~ or more contributor license agreements.  See the NOTICE file
  ~ distributed with this work for additional information
  ~ regarding copyright ownership.  The ASF licenses this file
  ~ to you under the Apache License, Version 2.0 (the
  ~ "License"); you may not use this file except in compliance
  ~ with the License.  You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing,
  ~ software distributed under the License is distributed on an
  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  ~ KIND, either express or implied.  See the License for the
  ~ specific language governing permissions and limitations
  ~ under the License.
  -->

<!-- EhCache XML configuration file used for Shiro spring sample application -->
<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

If the path is a Java System Property it is replaced by
its value in the running VM.

The following properties are translated:
user.home - User's home directory
user.dir - User's current working directory
java.io.tmpdir - Default temp file path -->
    <diskStore path="java.io.tmpdir/shiro-spring-sample"/>


    <!--Default Cache configuration. These will applied to caches programmatically created through
    the CacheManager.

    The following attributes are required:

    maxElementsInMemory            - Sets the maximum number of objects that will be created in memory
    eternal                        - Sets whether elements are eternal. If eternal,  timeouts are ignored and the
                                     element is never expired.
    overflowToDisk                 - Sets whether elements can overflow to disk when the in-memory cache
                                     has reached the maxInMemory limit.

    The following attributes are optional:
    timeToIdleSeconds              - Sets the time to idle for an element before it expires.
                                     i.e. The maximum amount of time between accesses before an element expires
                                     Is only used if the element is not eternal.
                                     Optional attribute. A value of 0 means that an Element can idle for infinity.
                                     The default value is 0.
    timeToLiveSeconds              - Sets the time to live for an element before it expires.
                                     i.e. The maximum time between creation time and when an element expires.
                                     Is only used if the element is not eternal.
                                     Optional attribute. A value of 0 means that and Element can live for infinity.
                                     The default value is 0.
    diskPersistent                 - Whether the disk store persists between restarts of the Virtual Machine.
                                     The default value is false.
    diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
                                     is 120 seconds.
    memoryStoreEvictionPolicy      - Policy would be enforced upon reaching the maxElementsInMemory limit. Default
                                     policy is Least Recently Used (specified as LRU). Other policies available -
                                     First In First Out (specified as FIFO) and Less Frequently Used
                                     (specified as LFU)
    -->

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />

    <!-- We want eternal="true" (with no timeToIdle or timeToLive settings) because Shiro manages session
expirations explicitly.  If we set it to false and then set corresponding timeToIdle and timeToLive properties,
ehcache would evict sessions without Shiro's knowledge, which would cause many problems
(e.g. "My Shiro session timeout is 30 minutes - why isn't a session available after 2 minutes?"
Answer - ehcache expired it due to the timeToIdle property set to 120 seconds.)

diskPersistent=true since we want an enterprise session management feature - ability to use sessions after
even after a JVM restart.  -->
    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           eternal="true"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>

    <cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
           maxElementsInMemory="100"
           eternal="false"
           timeToLiveSeconds="600"
           overflowToDisk="false"/>

</ehcache>

6.建shiro部分的数据库

配置文部分已经完成,接下来就是建数据库了,shiro的数据库结构大概包含三部分:用户、角色、权限。至于怎么设计数据结构,自己定义,因为查询用户和角色权限的sql代码都是自己写,所以数据库表的字段名什么的没有固定的,自己设计。

我这里设计的是5个表:用户sys_user、角色sys_role、权限sys_permission、用户角色sys_user_role、角色权限sys_role_permission

用逆向工程生成pojo、接口、xml

7.自定义realm

接下来就是自定义Realm了,新建文件MyRealm,这个类继承AuthorizingRealm(shiro官方类),主要实现两个方法,一个验证(登录操作时调用subject.login(token)时候系统自动调用),一个授权(也是在登录时调用,获取权限和角色信息放到SimpleAuthorizationInfo里返回,如果没有设置缓存,所有需要权限验证的方法标签都得执行这个方法,如果设置了缓存就只执行一次)

MyRealm里包含了一个方法setCredentialMatcher(),它是在applicationContext-shiro.xml配置文件里配置的一个初始化方法,读的就是这个方法,web容器初始化时执行的,是密码验证时先进行的加密算法和散列次数

MyRealm.java代码

package com.liqiye.system;

import com.liqiye.bean.*;
import com.liqiye.service.RealmService;
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * @author liqiye
 * @description 自定义shiro的realm,用于验证与授权
 * @date 2019/2/15
 */
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private RealmService realmService;


    /**
     * 授权:
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录信息,这里获取的是下面验证方法里的SimpleAuthenticationInfo的第一个参数
        User user = (User)principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获取角色
        Set<Role> roles = realmService.getRolesByUser(user);
        for (Role role:roles) {
            // addRole放入的是字符串,这里放进去什么都可以,中文字符也行。
            // 每个字符串与前端<shiro:hasRole name="">标签name里面的值做比较
            info.addRole(role.getExpression());
            //获取权限
            Set<Permission> permissions = realmService.getPermissionsByRole(role);
            for (Permission permission : permissions) {
                info.addStringPermission(permission.getExpression());
            }
        }
        return info;
    }

    /*
     * 用户验证
     */
    @Override
    protected AuthenticationInfo  doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1. token 中获取登录的 username! 注意不需要获取password.
        String username = (String)token.getPrincipal();
        //通过用户名获取用户信息
        User user = realmService.getUserByUsername(username);
        if(user==null){
            //用户名不存在
            return null;
        }
        //3.设置盐值 ,(加密的调料,让加密出来的东西更具安全性,一般是通过数据库查询出来的。 简单的说,就是把密码根据特定的东西而进行动态加密,如果别人不知道你的盐值,就解不出你的密码)
        String source = "admin";
        ByteSource credentialsSalt = new Md5Hash(source);
        //当前 Realm 的name
        String realmName = getName();
        //返回值实例化
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, realmName);
        return info;
    }

    //init-method 配置.
    public void setCredentialMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");//MD5算法加密
        credentialsMatcher.setHashIterations(1024);//1024次循环加密
        setCredentialsMatcher(credentialsMatcher);
    }


    //用来测试的算出密码password盐值加密后的结果,下面方法用于新增用户添加到数据库操作的,这里就直接用main获得,直接添加到数据库
    public static void main(String[] args) {
        String saltSource = "admin";
        String hashAlgorithmName = "MD5";
        String credentials = "123456";
        ByteSource salt = new Md5Hash(saltSource);
        int hashIterations = 1024;
        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
        System.out.println(result);
    }
}

8.自定义RealmService

上面的Realm还用到了自定义的RealmService,其实只要可以获取用户信息、获取权限角色信息就可以了,sql语句我这里就简单用逆向工程的代码,没有设计出效率好的sql语句,因为是第一次自己整合shiro,先成功再说

RealmService.java 代码

package com.liqiye.service;

import com.liqiye.bean.*;
import com.liqiye.dao.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author liqiye
 * @description 自定义realm的Service
 * @date 2019/2/15
 */
@Service
public class RealmService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private RolePermissionMapper rolePermissionMapper;

    @Autowired
    private PermissionMapper permissionMapper;


    /**
     * 通过用户名获取用户对象
     * @param username
     * @return
     */
    public User getUserByUsername(String username){
        UserExample example = new UserExample();
        UserExample.Criteria criteria = example.createCriteria();
        criteria.andUsernameEqualTo(username);
        User user = userMapper.selectByExample(example).get(0);
        return user;
    }

    /**
     * 通过user对象获取对应的角色集合
     * @param user
     * @return
     */
    public Set<Role> getRolesByUser(User user){
        UserRoleExample example = new UserRoleExample();
        UserRoleExample.Criteria criteria = example.createCriteria();
        criteria.andUserIdEqualTo(user.getId());
        List<UserRole> userRoles = userRoleMapper.selectByExample(example);
        Set<Role> roles=new HashSet<Role>();
        for(UserRole userRole:userRoles) {
            Role role = roleMapper.selectByPrimaryKey(userRole.getRoleId());
            roles.add(role);
        }
        return roles;
    }

    /**
     * 通过role对象获取permission权限集合
     * @param role
     * @return
     */
    public Set<Permission> getPermissionsByRole(Role role){
        RolePermissionExample example1 = new RolePermissionExample();
        RolePermissionExample.Criteria criteria1 = example1.createCriteria();
        criteria1.andRoleIdEqualTo(role.getId());
        List<RolePermission> rolePermissions = rolePermissionMapper.selectByExample(example1);
        Set<Permission> permissions=new HashSet<Permission>();
        for (RolePermission rolePermission : rolePermissions) {
            Permission permission = permissionMapper.selectByPrimaryKey(rolePermission.getPermissionId());
            permissions.add(permission);
        }
        return permissions;
    }

}

9. controller登录方法

然后就是编写登录controller方法,shiro帮我们封装好的登录验证方法为subject.login(token),用户名错误、密码错误、超过尝试次数等问题是直接抛出异常,我们在每个catch里面做处理和返回的页面即可

//登录逻辑方法
	@RequestMapping("/login")
	public String login(String username, String password, HttpSession session, Model model){
		if(username==null || username.equals("")){
			model.addAttribute("msg", "账号不可为空");
			return "loginPage";
		}
		if(password==null || password.equals("")){
			model.addAttribute("msg", "密码不可为空");
			return "loginPage";
		}
		Subject subject = SecurityUtils.getSubject();
		// 封装token
		UsernamePasswordToken token=new UsernamePasswordToken(username,password);
		try {
			//登录方法(认证是否通过)
			subject.login(token);
		} catch ( UnknownAccountException uae ) {
			//用户名错误
			model.addAttribute("msg", "用户名或者密码错误");
			return "loginPage";
		} catch ( IncorrectCredentialsException ice ) {
			//密码不正确
			model.addAttribute("msg", "用户名或者密码错误");
			return "loginPage";
		} catch ( LockedAccountException lae ) {
			model.addAttribute("msg", "账户已被锁定");
			return "loginPage";
		} catch ( ExcessiveAttemptsException eae ) {
			model.addAttribute("msg", "已超过登录次数");
			return "loginPage";
      	} catch ( AuthenticationException ae) {
			//登录异常
			model.addAttribute("msg", "用户名或者密码错误");
			return "loginPage";
		}
		return "mainPage";
	}

10.前端页面提交登录表单

<form action="${APP_PATH}/view/login" method="post">
        <p style="color: red">${msg}</p>
        用户名<input type="text" name="username"/><br>
        密码<input type="password" name="password"/><br>
        <button type="submit">登录</button>
    </form>

这是我第一次写博客,我的配置文件也是引用别人的,算是记录下学习的旅程!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值