springboot-shiro chapter03 login

简介:这一节主要是集成shiro进行鉴权, 用户登录/退出

环境:

IDEA15+
JDK1.8+
Maven3+

代码:

https://git.oschina.net/xinshu/springboot-shiro

一、shiro 基本功能

http://shiro.apache.org/

shiro-01

Authentication:身份认证,验证用户身份;


Authorization:授权,验证某个已认证的用户是否拥有某个权限;如:验证某个用户是否拥有某个角色。或者验证某个用户对某资源是否具有某个权限;


Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;


Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;


Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;


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


Testing:提供测试支持; 


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


Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

二、shiro 流程及基本概念

shiro-02

Shiro的对外API核心就是Subject;

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;SecurityManager才是实际的执行者;


SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;


Realm:Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
 
一个简单的Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

三、shiro Authentication认证特点

Authentication is the process of identity verification– you are trying to verify a user is who they say they are. To do so, a user needs to provide some sort of proof of identity that your system understands and trusts.

The Shiro framework is designed to make authentication as clean and intuitive as possible while providing a rich set of features. Below is a highlight of the Shiro authentication features.

Subject Based - Almost everything you do in Shiro is based on the currently executing user, called a Subject. And you can easily retrieve the Subject anywhere in your code. This makes it easier for you to understand and work with Shiro in your applications.

Single Method call - The authentication process is a single method call. Needing only one method call keeps the API simple and your application code clean, saving you time and effort.

Rich Exception Hierarchy - Shiro offers a rich exception hierarchy to offered detailed explanations for why a login failed. The hierarchy can help you more easily diagnose code bugs or customer services issues related to authentication. In addition, the richness can help you create more complex authentication functionality if needed.

‘Remember Me’ built in - Standard in the Shiro API is the ability to remember your users if they return to your application. You can offer a better user experience to your them with minimal development effort.

Pluggable data sources - Shiro uses pluggable data access objects (DAOs), called Realms, to connect to security data sources like LDAP and Active Directory. To help you avoid building and maintaining integrations yourself, Shiro provides out-of-the-box realms for popular data sources like LDAP, Active Directory, and JDBC. If needed, you can also create your own realms to support specific functionality not included in the basic realms.

Login with one or more realms - Using Shiro, you can easily authenticate a user against one or more realms and return one unified view of their identity. In addition, you can customize the authentication process with Shiro’s notion of an authentication strategy. The strategies can be setup in configuration files so changes don’t require source code modifications– reducing complexity and maintenance effort.

 

四、依赖资源

与上一节相比,这里主要依赖shiro-spring构建,由于shiro-spring构建又依赖于shiro-core及shiro-web控件,所以pom.xml文件只需依赖shiro-spring即可

screenshot

<dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>${shiro.version}</version>
</dependency>

 

五、配置及实现

从Shiro工作流程中,我们需要配置SecurityManager(负责接收Subject校验请求、与其他组件交互)及Reaml(获取安全数据信息)。由于是用spring-boot, 这里推荐由配置传统的xml文件改为java 注解@Configuration方式配置,主要配置如下:

@Configuration
public class ShiroConfiguration {

    private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

    @Bean(name = "ShiroRealmImpl")
    public ShiroRealmImpl getShiroRealm() {
        return new ShiroRealmImpl();
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(getShiroRealm());
        return defaultWebSecurityManager;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean() {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager());

        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/welcome");
        filterChainDefinitionMap.put("/welcome", "authc");
        filterChainDefinitionMap.put("/**", "anon");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
}

其中:

Spring中使用shiro时, Spring容器会根据bean名称查找shiroFilter的bean,同时将所有Filter操作委托给它,shiroFilter作为入口定义了需要控制的URL规则类型,然后根据URL规则进行相应的安全控制,具体shiroFilter定义过程如下:

1.Spring中定义了一类特殊的Bean——FactoryBean,当需要“shiroFilter”的bean时, 会调用ShiroFilterFactoryBean的getObject()方法来实现

ShiroFilterFactoryBean

很显然ShiroFilterFactoryBean定义过程中需要指定Securitymanager, successUrl, loginUrl以及filterChainDefinitionMap等。

2.定义securityManager bean, 指定securitymanager类型,同时设置安全数据的获取方式setRealm(Realm realm)

3.定义Realm的bean,ShiroRealmImpl类具体实现如下:
public class ShiroRealmImpl extends AuthenticatingRealm {

    private static Logger logger = LoggerFactory.getLogger(ShiroRealmImpl.class);

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;

        String username = usernamePasswordToken.getUsername();
        String encodedPassword = new Sha256Hash(usernamePasswordToken.getPassword()).toBase64();

        logger.info("------------------------authentication--------------------------");


        if (false) {
            // TODO 用户名不存在
            throw new UnknownAccountException();

        } else if (false) {
            // TODO 密码错误
            throw new IncorrectCredentialsException();
        }

        return new SimpleAuthenticationInfo(new ShiroUser(username, "用户编码", "用户名称"),
                String.valueOf(usernamePasswordToken.getPassword()),
                ByteSource.Util.bytes("用户id"), getName());
    }
}
主要是实现了doGetAuthenticationInfo(AuthenticationToken token)方法,来验证当前用户身份信息
4.另外值得说明的是这里定义了,lifecycleBeanPostProcessor Bean这里主要的目的是管理shiro的生命周期
5.Spring mvc 登录/退出Controller, 
请求/login时, http谓语为GET类型则加载login.jsp页面,POST类型作为验证用户身份的请求,调用subject.login(usernamePasswordToken)成功重定向到welcome.jsp, 否则重定向到login.jsp页面
@Controller
public class LoginController {
    private static Logger logger = LoggerFactory.getLogger(LoginController.class);

    @Value(value = "${application.message}")
    private String message;

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String loginInit(Map<String, Object> model) {
        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(Map<String, Object> model, @RequestParam("userid") String userId,
                        @RequestParam("password") String password) {

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userId, password);

        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException uae) {
            logger.info("未知账户");
        } catch (IncorrectCredentialsException ice) {
            logger.info("密码不正确");
        } catch (LockedAccountException lae) {
            logger.info("账户已锁定");
        } catch (ExcessiveAttemptsException eae) {
            logger.info("用户名密码次数过多");
        } catch (AuthenticationException ae) {
            logger.info("用户名密码不正确");
        }

        if (subject.isAuthenticated()) {
            logger.info("登录认证通过");
            model.put("time", new Date());
            model.put("message", this.message);
            return "redirect:/welcome";
        } else {
            usernamePasswordToken.clear();
            return "redirct:/login";
        }
    }

    @RequestMapping(value="/logout", method=RequestMethod.POST)
    public String logout(){
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }
}
 
 
 
 
原文地址:https://www.cnblogs.com/i-bugs/p/5395299.html