shiro+SpringMVC集成

一.本文主要是本人对使用shiro方面的总结。重点在介绍shiro使用。

二.1.导入所需要的架包

maven导入

 
  

    org.apache.shiro
    shiro-web
    1.3.2


    org.apache.shiro
    shiro-aspectj
    1.3.2


    org.apache.shiro
    shiro-spring
    1.3.2


    org.apache.shiro
    shiro-ehcache
    1.3.2

2.shiro的xml配置

spring-context-shiro.xml

xml version="1.0" encoding="UTF-8"?>
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">

    

    
    id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"> 

    
    id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        
        name="realm" ref="jdbcRealm"> 
        
        name="cacheManager" ref="cacheManager"/>
    

    
    id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    

    
    class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        name="securityManager" ref="securityManager"/>
    

    
    
        
    

    
    id="credentialsMatcher"
          class="shiro.RetryLimitHashedCredentialsMatcher">
        ref="cacheManager" />
        name="hashAlgorithmName" value="md5" />
        
        name="hashIterations" value="3" />
        name="storedCredentialsHexEncoded" value="true" />
    

    
    
        
        
        
        
        
        
        
        
    

    
    
        
            
                
                
                
                
            
        
        
        
        
                  
        
        
                  
                   
                   
                    
        
        
                  
                   
                    
                     
        
        
        
        
        
        
    

    id="jdbcRealm" class="shiro.JdbcRealmCustom">
        name="credentialsMatcher">
            class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                
                name="hashAlgorithmName" value="MD5"> 
                
                name="hashIterations" value="1024"> 
            
        
        
        name="authenticationCachingEnabled" value="true" />
        name="authenticationCacheName" value="authenticationCache" />
        name="authorizationCachingEnabled" value="true" />
        name="authorizationCacheName" value="authorizationCache" />
    



    
    id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        name="redirectUrl" value="/login" />
    

    
    
          
    
        
    

    
    
    id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        
        name="securityManager" ref="securityManager"> 
        
        name="loginUrl" value="/login"/>
        
        name="successUrl" value="/index"/>
        
        name="unauthorizedUrl" value="/unauth"/>
        name="filterChainDefinitions">
            
                
                /resources/** = anon
                /resource/** = anon
                
                /login* = anon
                /register* = anon
                
                /logout = logout
                
                 
                
                 /admin*=authc , roles[admin]
                
                /** = authc
            
        
    

3.web的xml中过滤器配置,注意过滤器名称需要和配置文件中Bean中过滤器名称相同



    shiroFilter
    org.springframework.web.filter.DelegatingFilterProxy
    
        targetFilterLifecycle
        true
    


    shiroFilter
    /*

shiro的配置基本完成。

三、使用讲解。

使用shiro一般需要自己实现 shiro中的 AuthorizingRealm 方法。也可以直接使用shiro自己实现的方法,

例如:JdbcRealm   本人实现的方法名称是:JdbcRealmCustom(有点类似了!),本人都做了尝试,感觉还是shiro自己实现的方法JdbcRealmCustom,对新手容易上手点。但是自己实现的时候在配置缓存时候方便。

shiro+SpringMVC集成_第1张图片

3.1 使用shiro自己实现的方法JdbcRealm,这个方法思路是直接写sql语句获得user信息,其他不用理会,shiro已经自己实现了

,直接看源码就明白使用方式了:

//TODO - complete JavaDoc

/*--------------------------------------------
|             C O N S T A N T S             |
============================================*/
/**
 * 定义了一些常量,直接看sql语句  通过用户名获得用户密码,我这需要覆盖,因为表名不一样,以下几个同理
 */
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";

/**
 * 获得密码+盐
 */
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";

/**
 * 要角色
 */
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";

/**
 * The default query used to retrieve permissions that apply to a particular role.
 */
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";

private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);

/**
 * Password hash salt configuration. 
    *
  • NO_SALT - password hashes are not salted.
  • *
  • CRYPT - password hashes are stored in unix crypt format.
  • *
  • COLUMN - salt is in a separate column in the database.
  • *
  • EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called * to get the salt
*/ public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL}; /*-------------------------------------------- | I N S T A N C E V A R I A B L E S | ============================================*/ protected DataSource dataSource; protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY; protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY; protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY; protected boolean permissionsLookupEnabled = false; protected SaltStyle saltStyle = SaltStyle.NO_SALT; /*-------------------------------------------- | C O N S T R U C T O R S | ============================================*/ /*-------------------------------------------- | A C C E S S O R S / M O D I F I E R S | ============================================*/ /** * Sets the datasource that should be used to retrieve connections used by this realm. *设置数据库,老一套了 * @param dataSource the SQL data source. */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Overrides the default query used to retrieve a user's password during authentication. When using the default * implementation, this query must take the user's username as a single parameter and return a single result * with the user's password as the first column. If you require a solution that does not match this query * structure, you can override {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)} or * just {@link #getPasswordForUser(java.sql.Connection,String)} * 对外set方法了可以覆盖, * @param authenticationQuery the query to use for authentication. * @see #DEFAULT_AUTHENTICATION_QUERY */ public void setAuthenticationQuery(String authenticationQuery) { this.authenticationQuery = authenticationQuery; } /** * Overrides the default query used to retrieve a user's roles during authorization. When using the default * implementation, this query must take the user's username as a single parameter and return a row * per role with a single column containing the role name. If you require a solution that does not match this query * structure, you can override {@link #doGetAuthorizationInfo(PrincipalCollection)} or just * {@link #getRoleNamesForUser(java.sql.Connection,String)} * * @param userRolesQuery the query to use for retrieving a user's roles. * @see #DEFAULT_USER_ROLES_QUERY */ public void setUserRolesQuery(String userRolesQuery) { this.userRolesQuery = userRolesQuery; } /** * Overrides the default query used to retrieve a user's permissions during authorization. When using the default * implementation, this query must take a role name as the single parameter and return a row * per permission with three columns containing the fully qualified name of the permission class, the permission * name, and the permission actions (in that order). If you require a solution that does not match this query * structure, you can override {@link #doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} or just * {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}

*

* Permissions are only retrieved if you set {@link #permissionsLookupEnabled} to true. Otherwise, * this query is ignored. * * @param permissionsQuery the query to use for retrieving permissions for a role. * @see #DEFAULT_PERMISSIONS_QUERY * @see #setPermissionsLookupEnabled(boolean) */ public void setPermissionsQuery(String permissionsQuery) { this.permissionsQuery = permissionsQuery; } /** * Enables lookup of permissions during authorization. The default is "false" - meaning that only roles * are associated with a user. Set this to true in order to lookup roles and permissions. * * @param permissionsLookupEnabled true if permissions should be looked up during authorization, or false if only * roles should be looked up. */ public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) { this.permissionsLookupEnabled = permissionsLookupEnabled; } /** * Sets the salt style. See {@link #saltStyle}. * * @param saltStyle new SaltStyle to set. */ public void setSaltStyle(SaltStyle saltStyle) { this.saltStyle = saltStyle; if (saltStyle == SaltStyle.COLUMN && authenticationQuery.equals(DEFAULT_AUTHENTICATION_QUERY)) { authenticationQuery = DEFAULT_SALTED_AUTHENTICATION_QUERY; } }

源码内容挺简单的,现在开始覆盖了。方式自然是xml加载bean了,IOC容器了,最简单,直接配置好spring启动时候就会自动 就加载完成。

配置过程上面spring-context-shiro.xml有,主要是这一块

name="dataSource" ref="dataSource"> 
name="authenticationQuery"
          value="SELECT password FROM T_USER WHERE user_name = ?">

name="userRolesQuery"
          value="select T_ROLE.ROLE_NAME
           from T_USER,T_USER_ROLE,T_ROLE where T_USER.USER_NAME= ?
           and T_USER.USER_ID=T_USER_ROLE.USER_ID
            and T_USER_ROLE.USER_ID=T_ROLE.ROLE_ID">

name="permissionsQuery"
          value="select T_RIGHT.RIGHT_NAME
           from T_ROLE,T_ROLE_RIGHT,T_RIGHT where T_ROLE.ROLE_NAME= ?
            and T_ROLE.ROLE_ID=T_ROLE_RIGHT.ROLE_ID
             and T_ROLE_RIGHT.RIGHT_ID=T_RIGHT.RIGHT_ID">

实际是sql语句书写,完成这一步时候,就要开始写登录部分了,shiro只是做权限、用户验证,主体逻辑还是要自己写,也很简单

@RequestMapping(value = "/login",method = { RequestMethod.GET, RequestMethod.POST })
public String login(User user,Model model) {
    if (isRelogin(user)){
        return  "index";
    }
    //用户为空时,直接跳转登录页面:解决启动为空登录报错
    if (StringUtil.isEmpty(user.getUserName())){
        model.addAttribute("failMsg", "用户不能为空!");
        return  "login";
    }

    // 组装token,包括用户名、密码
    UsernamePasswordToken usernamePasswordToken = new
            UsernamePasswordToken(user.getUserName(),user.getPassword());
    Subject subject = SecurityUtils.getSubject();

    // shiro登陆验证
    try {
        subject.login(usernamePasswordToken);
        org.apache.shiro.session.Session session = subject.getSession();
        session.setAttribute("userName", user.getUserName());
    } catch (Exception ex) {
        model.addAttribute("failMsg", "用户不存在或密码错误!");
        return"login";
    }

    return "index";
}

shiro将用户、密码组装成token,之后获得用户subject执行内部login方法,最终回去你xml配置的


name="realm" ref="jdbcRealm"> 

中进行登录的验证。同时shiro提供了各种抛出异常,可以使得异常提示更加准确。这里不详细叙述了。

至此第一方式完成。

3.2 使用自己实现的方法JdbcRealmCustom方法。

实际上和第一种非常类似,只要在配置文件中进行更改就,现在配置文件就是使用的第二种方式。

public class JdbcRealmCustom extends AuthorizingRealm {

    private Logger logger = LoggerFactory.getLogger("JdbcRealmCustom");

    @Autowired
    private UserService userService;
    @Autowired
    private EhCacheManager ehCacheManager;

    private static String userName;
    private User user;

    /**
     * 用户授权认证
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 从数据库中获取当前登录用户的详细信息
        if (user!=null){
//            根据用户名称获得用户角色  一个用户可以拥有多个角色
            List userRoles=userService.getRoleByUserId(user.getUserId());
            Set rightList= new HashSet();;
            Set roleList = new HashSet();;
            List rightNames ;
            for (KeyValudBean userRole:userRoles) {
                roleList.add(userRole.getValue());
                //查询对应角色的对应权限集合
                rightNames=userService.getRightByRoleId(userRole.getKey());
                for (KeyValudBean rightName : rightNames) {
                    if(StringUtils.isNotBlank(rightName.getValue())){
                        rightList.add(rightName.getValue());
                    }
                }
            }

            //赋角色
            info.addRoles(roleList);
            //赋权限
            info.addStringPermissions(rightList);
            return info;
        }
        return null;
    }

    /**
     * 用户登陆认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 如果已经登陆,无需重新登录
        //首先从缓存中获取记录
        EhCache cache = (EhCache) ehCacheManager.getCache("userNameAndPass");
        logger.info("======用户登陆认证======");
        //UsernamePasswordToken对象用来存放提交的登录信息
        userName = authenticationToken.getPrincipal().toString();
        String password;
        if (StringUtil.isEmpty((String)cache.get(userName))) {
            user=userService.queryUserByName(userName);
            password=user.getPassword();
        }else{
            password=(String)cache.get(userName);
            System.err.printf("**********************去缓存获得密码******************************");
        }
        //查出是否有此用户
//      缓存用户名和密码
        cache.put(userName,password);
        if(user!=null || StringUtil.isNotEmpty(password)){
            //若存在,将此用户存放到登录认证info中
            return new SimpleAuthenticationInfo(userName, password, getName());
        }
        return null;
    }



    /**
     * @Author:          郭航飞
     * @Description::已经登录处理
     * @CreateDate:   2018/4/25 17:55
     **/
    private boolean isRelogin(User user) {
        Subject us = SecurityUtils.getSubject();
        if (us.isAuthenticated()) {
            // 参数未改变,无需重新登录,默认为已经登录成功
            return true;
        }
        // 需要重新登陆
        return false;
    }

}

对比一下非常类似,只是更改成用java代码去层层调用获得数据。

四.对于缓存,暂时没有深入研究。就是简单的实现了一个样例。

所用源码在 https://github.com/guohangfei/my_project    

五、总结:

     两种方式都可以,新手建议先使用第一种进行使用。实际业务量不多情况第一种其实简单实用。

你可能感兴趣的:(java)