最近的生活,真是一言难尽。。。不多说了,直接进入正题吧。

以前做项目的时候,对于用户登录这边,基本用的就是拦截器,多身份得话,就在拦截器中进行判断。在浏览帖子的时候,看到了关于shiro的介绍,又正巧要做一个新的项目。于是在新的项目中,用户的权限管理就改用shiro去控制。

关于shiro,就不多介绍了,一个轻量的用户权限管理包;项目的要求也很简单。后台管理员和前台用户两个身份分别进行登录,访问各自的url。

首先,通过maven引入shiro的jar包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
	<version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>shiro-freemarker-tags</artifactId>
    <version>1.0.0</version>
</dependency>

然后,创建spring-shiro.xml文件,对shiro进行配置,需要注意的几个配置点:

多身份用户,对应多个realm对象

不同身份下的身份验证规则(FormAuthenticationFilter类或者重写的继承类)

shiro的主filter配置(ShiroFilterFactoryBean),包括filters(过滤规则)、filterChainDefinitions(设置url对应的过滤规则)

配置文件如下:

<!-- 配置filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"></property>
    <property name="filters">
        <map>
            <entry key="adminFilter" value-ref="AdminFilter"></entry>
            <entry key="userFilter" value-ref="UserFilter"></entry>
            <entry key="adminPermissionFilter" value-ref="AdminPermissionFilter"></entry>
        </map>
    </property>
    <property name="filterChainDefinitions">
        <value>
            /static/**=anon
            /admin/base/login/*=anon
            /index=anon
            /admin/**=adminFilter,adminPermissionFilter
            /ucenter/auth/**=anon
            /ucenter/**=userFilter
        </value>
    </property>
</bean>

<!-- 配置securityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 引入ModularRealmAuthenticator类 -->
    <property name="authenticator" ref="CustomerModularRealmAuthenticator"></property>
    <!-- 配置多个realm对象 -->
    <property name="realms">
        <list>
            <ref bean="AdminRealm"></ref>
            <ref bean="TeacherRealm"></ref>
            <ref bean="UserRealm"></ref>
        </list>
    </property>
</bean>
<!-- 自定义realm -->
<!-- 后台对象realm -->
<bean id="AdminRealm" class="com.bc.core.shiro.realm.AdminRealm"></bean>
<!-- 教师端realm -->
<bean id="TeacherRealm" class="com.bc.core.shiro.realm.TeacherRealm"></bean>
<bean id="UserRealm" class="com.bc.core.shiro.realm.UserRealm"></bean>

<!-- 配置filter对应的登陆页面 -->
<bean id="AdminFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
    <property name="loginUrl" value="/admin/base/login/index"></property>
</bean>
<bean id="UserFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
    <property name="loginUrl" value="/ucenter/auth/login"></property>
</bean>

<!-- 自定义后台权限验证filter -->
<bean id="AdminPermissionFilter" class="com.bc.core.shiro.authz.AdminPermissionFilter">
    <property name="loginUrl" value="/admin/base/login/index"></property>
    <property name="unauthorizedUrl" value="/admin/main/error/authz"></property>
</bean>

<!-- 自定义ModularRealmAuthenticator类,选则当前要使用的realm -->
<bean id="CustomerModularRealmAuthenticator" class="com.bc.core.shiro.authc.CustomerRealmAuthenticator">
    <property name="authenticationStrategy">
        <!-- 配置认证策略,只要有一个Realm认证成功即可,并且返回所有认证成功信息 -->
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    </property>
</bean>
<!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

在用户登录的时候,需要把用户的登录名、密码传入到UsernamePasswordToken类中,由于我们是两种身份不同的用户,所以需要对UsernamePasswordToken类进行拓展,增加loginType参数

//UsernamePasswordToken拓展类
public class CustomerToken extends UsernamePasswordToken {

    private TokenTypeEnum loginType;

    public CustomerToken(String username,String password,TokenTypeEnum loginType){
        super(username,password);
        this.loginType = loginType;
    }

    public CustomerToken(String username,String password,TokenTypeEnum loginType,boolean rememberme){
        super(username,password,rememberme);
        this.loginType = loginType;
    }

    public TokenTypeEnum getLoginType() {
        return loginType;
    }

    public void setLoginType(TokenTypeEnum loginType) {
        this.loginType = loginType;
    }

}

//loginType枚举类
public enum TokenTypeEnum {

    Admin("Admin"),User("User");

    private String type;

    TokenTypeEnum(String type){
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

}

这样在登录时候原本调用的UserPasswordToken可以改为新的自定义类,并增加LoginType参数

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new CustomerToken(username,password,TokenTypeEnum.Admin);
subject.login(usernamePasswordToken);

不同身份对应的用户信息验证,在自定义的realm类中,进行实现

//继承AuthorizingRealm类,实现自己的逻辑
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private IUserDao userDao;

    @Autowired
    private IUserBaseDao userBaseDao;

    @Autowired
    private SessionDAO sessionDAO;

    //权限注入函数
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //用户认证函数
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取登录用户名
        String username = (String)authenticationToken.getPrincipal();
        UserEntity userEntity = userDao.getOne(MapUtil.getMap("mobile",username,"isdel",0));
        if(userEntity == null){
            throw new UnknownAccountException("用户名或密码错误!");
        }
        //验证密码是否正确
        char[] pass = (char[])authenticationToken.getCredentials();
        String password = String.valueOf(pass);
        if(!userEntity.getPassword().equals(MD5.encryption(password))){
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        //验证成功,赋值对象属性
        User user = new User();
        BeanUtils.copyProperties(userEntity,user);
        UserBaseEntity userBaseEntity = userBaseDao.getOne(MapUtil.getMap("userid",userEntity.getId()));
        if(userBaseEntity!= null){
            user.setName(userBaseEntity.getName());
            user.setAvatar(userBaseEntity.getAvatar());
        }

        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(admin,pass,getName());

        return simpleAuthenticationInfo;
    }
}

至此,多身份的登录流程就写完了,但是,突然发现在同一个浏览器登录两个身份的时候,前一个SimpleAuthenticationInfo中的principal对象会报错,后一个把前一个替换掉了,再去访问前一个的话,就会出现对象类型错误。

研究了一阵子。。虽然说是解决了,但是不知道其他人是怎么弄的,网上找了好多,也没找到类型的问题(是我找的不对吗)。

下一篇文章,去说一下我的解决办法。