安全框架的使用:Shiro

  created  by  鱼鱼 {{tag}}
创建于 2018年10月08日 14:22:44 最后修改于 2019年09月29日 10:39:00

安全框架的使用:Shiro

安全框架的使用:Shiro

    Shiro与Sping Security均是java的安全框架,主要用于处理用户身份验证和授权。

验证与授权

    常见场景为用户系统登录

shiro

    Shiro易用性强,提供了认证,授权,加密,和会话管理功能。

    Shiro的三大核心组件 :

    Subject:即当前用户概念,不止代表着某用户,也可以是进程或任何可能的事物。

    SecurityManager:即所有Subject的管理者,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。作用类似于SpringMVC中的DispatcherServlet,用于拦截所有请求并进行处理。

    Realm:Realm是用户的信息认证器和用户的权限认证器,我们需要自己来实现Realm来自定义的管理我们自己系统内部的权限规则。SecurityManager要验证用户,需要从Realm中查询,它可以算是安全上的DAO,其封装了很多的类似数据库连接和数据源。

    主要有以下几个功能点:

    Authentication:身份认证(登录);

    Authorzation:授权,判断用户是否由某个具体的权限;

    Session Manager:会话管理;

    Cryptography:加密,保护数据安全性,敏感数据密文存储;

    WebSupport:web项目支持;

    Caching:缓存,部分场景下提高数据存取效率;

    Concurrency:多应用多线程的并发管理(权限共享);

    RememberMe:配置后可以自动登陆。

    配置文件

    可以使用ini配置,但是在Spring项目中,这并不是最佳的配置方案。手动配置如下:

        xml文件常规配置

    1.配置web.xml文件

<!-- Shiro filter-->    
<filter>   
    <filter-name>shiroFilter</filter-name>   
    <filter-class>   
        org.springframework.web.filter.DelegatingFilterProxy    
    </filter-class>    
</filter>    
<filter-mapping>    
    <filter-name>shiroFilter</filter-name>    
    <url-pattern>/*</url-pattern>    
</filter-mapping>

    2.配置applicationContext.xml(略去部分内容)

<!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java --> 
<bean id="tmpmyRealm" class="com.jadyer.realm.AuthRealm"/> 
  
<!-- 设置自定义的Realm应用 -->  
<bean id="tmpsecurityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
    <property name="realm" ref="myRealm"/>
    <property name="cacheManager" ref="shiroCacheManager"/>
    <property name="rememberMeManager" ref="shiroRemembarMeManager"/>   
    <property name="realm" ref="myRealm"/> 
</bean>  
 
<bean id="tmpshiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 安全管理器 -->  
    <property name="securityManager" ref="securityManager" /> 
    <!-- 一些操作的url -->  
    <property name="loginUrl" value="/login" />  
    <property name="successUrl" value="/login/loginSuccessFull" />  
    <property name="unauthorizedUrl" value="/login/unauthorized" />
    <!-- 自定义filter配置 -->
        <property name="filters">
            <map>
                <entry key="authc">
                    <bean class="com.onefish.filter.CustomFormAuthenticationFilter"></bean>
                </entry>
            </map>
        </property>
    <!--  anon不需要认证 authc需要认证 user 验证通过或者rememberMe可以 -->
    <property name="filterChainDefinitions">  
        <value>  
            /home* = anon  
            / = anon  
            /site/** = user
            /logout = logout  
            /role/** = roles[admin,system]  
            /vendor/** = perms[permssion:look]  
            /** = authc  
        </value>  
    </property>  
</bean>

        Spring boot项目中的配置

/**
 * shiro的配置类
 * @author Administrator
 *
 */

@Configuration
public class ShiroConfiguration {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登录的url和登录成功的url
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/");
        //配置访问权限
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/tool", "anon");    //配置权限
        filterChainDefinitionMap.put("/blogEditor", "anon");
        filterChainDefinitionMap.put("/templates/*.*", "authc");
        filterChainDefinitionMap.put("/logout", "logout");        //登出接口,自带过滤器已经实现基本的信息清除
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

    //配置核心安全事务管理器
    @Bean(name = "securityManager")
    public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {
        System.err.println("--加载shiro--");
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    //配置自定义的权限登录器
    @Bean(name = "authRealm")
    public AuthRealm authRealm(@Qualifier("credentialsMatcher") CredentialsMatcher matcher) {
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }

    //配置自定义的密码比较器
    @Bean(name = "credentialsMatcher")
    public CredentialsMatcher credentialsMatcher() {
        return new CredentialsMatcher();
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor 
    authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager manager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(manager);
        return advisor;
    }
}

    重写Realm

        重写Realm类,指定授权和认证的方式  下面是一个最简单的Realm

@Service
public class AuthRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    //认证.登录
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken utoken=(UsernamePasswordToken) token;//获取用户输入的token
        String username = utoken.getUsername();
        User user = userService.findUserByName(username);
        if(user!=null) {
            return new SimpleAuthenticationInfo(user, user.getPswd(), this.getClass().getName());
            //放入shiro.调用CredentialsMatcher检验密码
            //注意这里 第一个对象是放入session用于后面校验用户的 在这选取了整个user对象
        }else{
            return null;
        }
    }
    
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user=(User) principal.fromRealm(this.getClass().getName()).iterator().next();//获取session中的用户
        List<String> permissions=new ArrayList<>();
        Set<Role> roles = user.getRoles();
        if(roles.size()>0) {
            for(Role role : roles) {
                /*
                Set<Module> modules = role.getModules();
                if(modules.size()>0) {
                    for(Module module : modules) {
                        permissions.add(module.getMname());
                    }
                }
                */
                permissions.add(role.getName());
            }
        }
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);//将权限放入shiro中.
        return info;
    }

}

    重写、添加其余依赖

        重写密码比较器

@Service
public class CredentialsMatcher extends SimpleCredentialsMatcher {
    @Autowired
    EncodeFacade encodeFacade;
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken utoken=(UsernamePasswordToken) token;
        //获得用户输入的密码
        String inPassword = new String(utoken.getPassword());
        //获得数据库中的密码
        String dbPassword=(String) info.getCredentials();
        //进行密码的比对
        encodeFacade.checkPsw(inPassword,dbPassword,utoken.getUsername());
        return this.equals(inPassword, dbPassword);
    }

    编写登录接口

        写登陆的认证,注意此处异常的处理

@RequestMapping(value="/login",method=Request.POST)
public String login(@RequestBody User user){
     Subject subject=SecurityUtils.getSubject();
     UsernamePasswordToken userToken=new UsernamePasswordToken(user.getName(),user.getPassword());
     try{
            subject.login(userToken);
            return "登陆成功";
        }catch (UnknownAccountException e){
            return "用户名不存在";
        }catch (IncorrectCredentialsException e){
            return "密码错误";
        }
}

    注意各异常的分类和异常的捕获处理:

均是AuthenticationException的子类异常:

    UnknownAccountException:错误的账号;

    LockedAccountException:锁定的账号;

    DisabledAccountException:禁用的账号;

    IncorrctCredentialsException:错误的凭证;

    ExpiredCredentialsException:过期的凭证;

    ExcessiveAttemptsEXception:失败次数过多;

    前端页面此处略

    其他操作

    一些常用操作: 获取当前subject(用户)标识,即前面new  SimpleAuthenticationInfo中第一个参数

    (User)SecurityUtils.getSubject().getPrincipal();

Spring Security

    与Spring系列框架期和良好,功能较为强大。

    maven引入

<dependency> 
    <groupId>org.springframework.security</groupId> 
    <artifactId>spring-security-web</artifactId> 
    <version>{spring-security-version}</version> 
</dependency> 
<dependency> 
    <groupId>org.springframework.security</groupId> 
    <artifactId>spring-security-config</artifactId> 
    <version>{spring-security-version}</version> 
</dependency>

    定义认证逻辑

    配置Config类,继承WebSecurityConfigurerAdapter类,并重写该类原有部分方法

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {       
       @Override    //
       protected void configure(AuthenticationManagerBuilder auth){            
           super.configure(auth);
       }       
       @Override
       protected void configure(HttpSecurity http){ 
           super.configure(http);
       }       
       @Override
       protected void configure(WebSecurity web){            
           super.configure(web);
       }
}

    重写

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()                //  定义当需要用户登录时候,转到的登录页面
                .and()
                .authorizeRequests()       // 定义哪些URL需要被保护、哪些不需要被保护
                .anyRequest()              // 任何请求,登录后可以访问
                .authenticated();
    }

    

参考链接:权限控制框架-shiro


2019-09-29鱼鱼