SpringBoot2.1.6 + Shiro + Redis + MybatisPlus 整合


本文参考文献:https://www.cnblogs.com/caichaoqi/p/8900677.html#4371099

本文作为个人学习笔记整理,避免后期找不到处理方案:

1、pom.xml文件

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- shiro+redis缓存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>2.4.2.1-RELEASE</version>
        </dependency>
        <!-- shiro-spring -->
        <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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        
        <!-- MybatisPlus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        
        <!--Druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.57</version>
        </dependency>

2、application.yml 

server:
  port: 8080

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/shiro2?serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    database: 0
    host: localhost
    port: 6379
    timeout: 6000
    jedis:
      pool:
        max-active: 1000
        max-idle: 10
        min-idle: 5
        max-wait: -1
    
mybatis:
  mapper-locations:
  - classpath:SQL/**/*Mapper.xml
  type-aliases-package: xyz.ljcc.shiro.entity
  
mybatis-plus:
  global-config:
    db-config:
      logic-not-delete-value: 0
      logic-delete-value: 1
 
logging:
  level:
    xyz: 
      ljcc: 
        shiro: 
          mapper: debug
  

3、实体类

@Data
@NoArgsConstructor // 无参构造器
@AllArgsConstructor // 有参构造器
@TableName("sys_user")
public class SysUser implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Integer id;  // 主键
    
    @TableField(value = "login_name")
    private String loginName;   // 登录名
    
    @TableField(value = "pass_word") 
    private String password;  // 密码
    
    private String salt; // 盐值
    
    @TableLogic  // 删除标记
    private Integer enable;
    
    @TableField(exist = false)
    private Set<SysRole> roles = new HashSet<SysRole>();
    
    public String getCredentialsSalt() {
        return this.loginName + this.salt;
    }

}
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_role")
public class SysRole implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField(value = "role_name")
    private String roleName;

    @TableField(value = "role_desc")
    private String roleDesc;

    @TableLogic
    private Integer enable;

    @TableField(exist = false)
    private Set<SysResource> resources = new HashSet<SysResource>();

}
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_resource")
public class SysResource implements Serializable{
    
    private static final long serialVersionUID = 1L;

    @TableId(type=IdType.AUTO)
    private Integer id;
    
    @TableField(value="rec_name")
    private String recName;
    
    @TableField(value="rec_url")
    private String recUrl;
    
    @TableField(value="rec_type")
    private String recType;
    
    @TableField(value="parent_id")
    private Integer parentId;
    
    @TableField(value="rec_sort")
    private Integer recSort;
    
    @TableLogic
    private Integer enable;
    
}

4、Mapper层处理

public interface SysUserMapper extends BaseMapper<SysUser>{
    
    // 获取用户具体信息 【包含:角色 + 资源】 
    @Select(" SELECT * FROM SYS_USER WHERE id = #{id} ")
    @Results(id="userRoleResourceMap",value= {
        @Result(column="id",property="id",id=true),
        @Result(column="login_name",property="loginName"),
        @Result(column="pass_word",property="password"),
        @Result(column="salt",property="salt"),
        @Result(column="enable",property="enable"),
        @Result(property="roles",javaType=Set.class,column="id",
            many=@Many(select="xyz.ljcc.shiro.mapper.SysRoleMapper.getRoleByUserId")
        )
    })
    SysUser findUserInfo(Integer id);
}
public interface SysRoleMapper extends BaseMapper<SysRole> {

    @Results({
        @Result(id=true,column="id",property="id"),
        @Result(column="role_name",property="roleName"),
        @Result(column="role_desc",property="roleDesc"),
        @Result(column="enable",property="enable"),
        @Result(property="resources",javaType=Set.class,column="id",
            many=@Many(select="xyz.ljcc.shiro.mapper.SysResourceMapper.getResourceByRoleId")
        )
    })
    @Select(" SELECT R.* FROM SYS_ROLE R,SYS_USER_ROLE UR WHERE R.id = UR.role_id " + 
            " AND UR.user_id = #{userId} ")
    Set<SysRole> getRoleByUserId(Integer userId);
    
}
public interface SysResourceMapper extends BaseMapper<SysResource> {
    
    @Select(" SELECT R.* FROM SYS_RESOURCE R,SYS_ROLE_RESOURCE UR WHERE R.id = UR.resource_id " + 
            " AND UR.role_id = #{roleId} ")
    Set<SysResource> getResourceByRoleId(Integer roleId);
    
}

5、Service层处理

public interface SysUserService {
    
    SysUser findByLoginName(String loginName);
    
    /**
     * 获取用户明细
     * 包含:角色,资源
     * @param userId
     * @return
     */
    SysUser findUserInfo(Integer userId);
    
}
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> 
             implements SysUserService {

    @Override
    public SysUser findByLoginName(String loginName) {
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<SysUser>();
        queryWrapper.eq("login_name", loginName);
        return baseMapper.selectOne(queryWrapper);
    }

    @Override
    public SysUser findUserInfo(Integer userId) {
        return baseMapper.findUserInfo(userId);
    } 
}

6、配置文件处理

6.1、Shiro配置

package xyz.ljcc.shiro.config;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import xyz.ljcc.shiro.filter.KickoutSessionControlFilter;
import xyz.ljcc.shiro.realm.MyShiroRealm;

@Configuration
public class ShiroConfig {

    private final String loginUrl = "/auth/login";
    private final String CACHE_KEY = "shiro:cache:";
    private final String SESSION_KEY = "shiro:session:";
    private final int EXPIRE = 1800;
    
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置登录页面跳转链接
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        
        // 自定义拦截器
        Map<String,Filter> filtersMap = new LinkedHashMap<String,Filter>();
        // 限制同一账户同时在线个数。
        filtersMap.put("kickout", kickoutSessionControlFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);
        
        // 权限控制
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 放开静态资源
        filterChainDefinitionMap.put("/static/**", "anon");
        // 放开登录请求、登录页面请求、验证码请求等
        filterChainDefinitionMap.put("/auth/login", "kickout,anon");  // kickout,anon
        filterChainDefinitionMap.put("/auth/logout", "logout");
        filterChainDefinitionMap.put("/auth/kickout", "anon");
        
        //对所有用户认证
        filterChainDefinitionMap.put("/**","kickout,user"); // authc,kickout
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        
        return shiroFilterFactoryBean;
    }
    
    @Bean
    public SecurityManager securityManager() {
        DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置Realm
        securityManager.setRealm(myShiroRealm());
        // 设置自定义session管理器  -- 使用Redis
        securityManager.setSessionManager(sessionManager());
        // 设置自定义缓存实现  -- 使用Redis
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }
    
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    
    /**
     * HashedCredentialsMatcher 配置加密方式
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 配置散列算法 使用MD5加密
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 设置散列次数
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }
    
    /**
     * 配置Shiro redisManager
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setExpire(EXPIRE); // 设置过期时间
        return redisManager;
    }
    
    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager cacheManager = new RedisCacheManager();
        cacheManager.setRedisManager(redisManager());
        cacheManager.setKeyPrefix(CACHE_KEY);
        return cacheManager;
    }
    
    /**
     * Session Manager
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }
    
    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setKeyPrefix(SESSION_KEY);
        return redisSessionDAO;
    }
    
    /**
     * 限制同一账号登录同时登录人数控制
     * @return
     */
    @Bean
    public KickoutSessionControlFilter kickoutSessionControlFilter() {
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        kickoutSessionControlFilter.setCacheManager(cacheManager());
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        kickoutSessionControlFilter.setKickoutAfter(false);
        kickoutSessionControlFilter.setMaxSession(1);
        kickoutSessionControlFilter.setKickoutUrl("/auth/kickout");
        return kickoutSessionControlFilter;
    }
    
    /**
     * 授权所用配置
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    
    /**
     * Shiro生命周期处理器
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    
}

6.2、MybatisPlus配置

package xyz.ljcc.shiro.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置方言
        paginationInterceptor.setDialectType("mysql");
        return paginationInterceptor;
    }
    
    /**
     * 3.1。1之后不需要配置
     * @return
     */
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }
    
}

6.3、全局异常处理

/**
 * 全局异常处理类
 * 
 * @author liucan
 *
 */
@ControllerAdvice
@Slf4j
public class CtrlExceptionHandler {

    // 拦截未授权页面
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    @ExceptionHandler(UnauthorizedException.class)
    public String handleException(UnauthorizedException e) {
        log.debug(e.getMessage());
        return "403";
    }

    // 拦截未认证
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    @ExceptionHandler(AuthorizationException.class)
    public String handleException2(AuthorizationException e) {
        log.debug(e.getMessage());
        return "403";
    }

}

7、自定义Realm

package xyz.ljcc.shiro.realm;

import java.util.Set;

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.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
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 lombok.extern.slf4j.Slf4j;
import xyz.ljcc.shiro.entity.SysResource;
import xyz.ljcc.shiro.entity.SysRole;
import xyz.ljcc.shiro.entity.SysUser;
import xyz.ljcc.shiro.service.SysUserService;

@Slf4j
public class MyShiroRealm extends AuthorizingRealm{
    
    @Autowired
    private SysUserService sysUserService;
    
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info(" --------- 执行 Shiro授权 --------- ");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUser userInfo = (SysUser) principals.getPrimaryPrincipal();
        userInfo = sysUserService.findUserInfo(userInfo.getId());
        Set<SysRole> roles = userInfo.getRoles();
        for (SysRole sysRole : roles) {
            authorizationInfo.addRole(sysRole.getRoleName());
            Set<SysResource> resources = sysRole.getResources();
            for (SysResource sysResource : resources) {
                authorizationInfo.addStringPermission(sysResource.getRecUrl());
            }
        }
        return authorizationInfo;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        log.info(" --------- 执行 Shiro认证 --------- ");
        UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
        String loginName = token.getUsername();
        // 获取数据库中对应的用户信息
        SysUser userInfo = sysUserService.findByLoginName(loginName);
        if(userInfo == null)
            return null;
        SimpleAuthenticationInfo authenticationInfo 
            = new SimpleAuthenticationInfo(
                    userInfo, 
                    userInfo.getPassword(), 
                    ByteSource.Util.bytes(userInfo.getCredentialsSalt()), 
                    this.getName()
            );
        return authenticationInfo;
    }

}

8、并发控制处理  -- 同一用户只允许在一处登录   -- 【拦截器】

package xyz.ljcc.shiro.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import com.alibaba.fastjson.JSON;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import xyz.ljcc.shiro.entity.SysUser;

/**
 * 限制并发人数登录
 * 
 * @author liucan
 *
 */
@Setter
@Slf4j
public class KickoutSessionControlFilter extends AccessControlFilter {

    private String kickoutUrl; // 踢出后到的地址
    private boolean kickoutAfter = false; // 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
    private int maxSession = 1; // 同一个帐号最大会话数 默认1
    private final String CACHE_KEY = "shiro:cache:";

    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;

    // 设置Cache的key的前缀
    public void setCacheManager(CacheManager cacheManager) {
        this.cache = cacheManager.getCache(CACHE_KEY);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            // 如果没有登录,直接进行之后的流程
            return true;
        }
        Session session = subject.getSession();
        String loginName = ((SysUser) subject.getPrincipal()).getLoginName();
        Serializable sessionId = session.getId();

        // 读取缓存 没有就存入
        Deque<Serializable> deque = cache.get(loginName);

        // 如果此用户没有session队列,也就是还没有登录过,缓存中没有
        // 就new一个空队列,不然deque对象为空,会报空指针
        if (deque == null) {
            deque = new LinkedList<Serializable>();
        }

        // 如果队列里没有此sessionId,且用户没有被踢出;放入队列
        if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
            // 将sessionId存入队列
            deque.push(sessionId);
            cache.put(loginName, deque);
        }

        // 如果队列里的sessionId数超出最大会话数,开始踢人
        while (deque.size() > maxSession) {
            Serializable kickoutSessionId = null;
            if (kickoutAfter) { // 如果踢出后者
                kickoutSessionId = deque.removeFirst();
            } else { // 否则踢出前者
                kickoutSessionId = deque.removeLast();
            }
            cache.put(loginName, deque);
            try {
                // 获取被踢出的sessionId的session对象
                Session kickoutSession = 
                        sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                if (kickoutSession != null) {
                    // 设置会话的kickout属性表示踢出了
                    kickoutSession.setAttribute("kickout", true);
                }
            } catch (Exception e) {
            }
        }

        // 如果被踢出了,直接退出,重定向到踢出后的地址
        if (session.getAttribute("kickout") != null) {
            // 会话被踢出了
            try {
                // 退出登录
                subject.logout();
            } catch (Exception e) {
            }
            
            saveRequest(request);

            Map<String, String> resultMap = new HashMap<String, String>();
            // 判断是不是Ajax请求
            if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
                resultMap.put("user_status", "300");
                resultMap.put("message", "您已经在其他地方登录,请重新登录!");
                // 输出json串
                out(response, resultMap);
            } else {
                // 重定向
                WebUtils.issueRedirect(request, response, kickoutUrl);
            }
            return false;
        }
        return true;
    }

    private void out(ServletResponse hresponse, Map<String, String> resultMap) throws IOException {
        try {
            hresponse.setCharacterEncoding("UTF-8");
            PrintWriter out = hresponse.getWriter();
            out.println(JSON.toJSONString(resultMap));
            out.flush();
            out.close();
        } catch (Exception e) {
            log.error("KickoutSessionFilter.class 输出JSON异常,可以忽略。");
        }
    }

}

8、Controller层业务

8.1、登录处理

package xyz.ljcc.shiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.alibaba.druid.util.StringUtils;

import lombok.extern.slf4j.Slf4j;
import xyz.ljcc.shiro.entity.SysUser;
import xyz.ljcc.shiro.utils.RequestUtils;

@Controller
@RequestMapping("/auth")
@Slf4j
public class LoginController {

    @PostMapping("/login")
    public String doLogin(String username,String password,Model model) {
        try {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
            SysUser userInfo = (SysUser) subject.getPrincipal();
            log.info(userInfo.toString());
        } catch (Exception e) {
            model.addAttribute("msg", "用户名或密码错误");
            return "login";
        }
        return "redirect:/auth/index";
    }
    
    @GetMapping("/login")
    public String login() {
        return "login";
    }
    
    @GetMapping("/index")
    public String loginSuccessMessage(Model model) {
        String username = "未登录";
        SysUser user = RequestUtils.currentLoginUser();
        if(user != null && !StringUtils.isEmpty(user.getLoginName())) {
            username = user.getLoginName();
        } else {
            return "redirect:/auth/login";
        }
        model.addAttribute("username", username);
        return "index";
    }
    
    // 被踢出后跳转的页面
    @GetMapping("/kickout")
    public String kickOut() {
        return "kickout";
    }
    
}

8.2、访问权限测试的链接

@RestController
public class TestController {

    @GetMapping("/user/list")
    public String userList() {
        return "user-list ....";
    }
    
    @RequiresPermissions(value= {"/user/del"})
    @GetMapping("/user/del")
    public String userDel() {
        return "user-del ....";
    }
    
    @GetMapping("/role/list")
    public String roleList() {
        return "role-list ....";
    }
}

9、工具类部分:

9.1、加密

/**
 * Shiro密码生产工具
 * @author liucan
 *
 */
public class ShiroPWDUtil {
    
    /**
     * 获取随机盐值 与 MD5加密密码
     * @param loginName
     * @param pswd
     * @return  String[] 长度为2   [0]-salt   [1]-pswd
     */
    public static String[] getMD5SaltVsPswd(String loginName,String pswd) {
        String[] strs = new String[2];
        String salt = UUID.randomUUID().toString().substring(0, 8);
        strs[0] = salt;
        int iterations = 2; // 加密次数   与 ShiroConfig里面的 HashedCredentialsMatcher进行对应
        Object result = new SimpleHash("MD5", 
                                    pswd, 
                                    ByteSource.Util.bytes(loginName + salt),  // 添加验证
                                    iterations);
        strs[1] = result.toString();
        return strs;
    }
    
}

9.2、Shiro工具

public class RequestUtils {
    
    /**
     * 获取当前登录的用户,若用户未登录,则返回未登录 json
     * @return
     */
    public static SysUser currentLoginUser() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            Object principal = subject.getPrincipals().getPrimaryPrincipal();
            if (principal instanceof SysUser) {
                return (SysUser) principal;
            }
        }
        return null;
    }
    
}

10、页面部分

  1、index.html

         

  2、login.html

   

   3、403.html

  

   4、kickout.html

  

 11、测试

     使用两个不同的浏览器:为了SeesionId不一致  

      http://localhost:8080/auth/index   -- 会自动返回登录页面

   1、在浏览器1中输入用户名密码进行登录

      2、在浏览器2中输入用户名密码进行登录

      3、刷新浏览器1的index页面

          会提示被踢出:

          

   Redis数据:

 

 

      

    

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM