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