Shiro 實戰教程(下)

1

注:該shiro教程來源於B站上的一個教程,由於源碼是付費的,我就不分享了,下篇講解springboot搭配shiro進行使用。

我的個人博客:

天涯志

我的公衆號:菜鳥小謝

8

6.整合SpringBoot項目實戰

6.0 整合思路

1

6.1 創建springboot項目

2

6.2 引入shiro依賴

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-starter</artifactId>
  <version>1.5.3</version>
</dependency>

6.3 配置shiro環境

0.創建配置類

3

1.配置shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){
  //創建shiro的filter
  ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  //注入安全管理器
  shiroFilterFactoryBean.setSecurityManager(securityManager);
 	
  return shiroFilterFactoryBean;
}
2.配置WebSecurityManager
@Bean
public DefaultWebSecurityManager getSecurityManager(Realm realm){
  DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  defaultWebSecurityManager.setRealm(realm);
  return defaultWebSecurityManager;
}
3.創建自定義realm

4

public class CustomerRealm extends AuthorizingRealm {
    //處理授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
		//處理認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws 
      																																		AuthenticationException {
        return null;
    }
}
4.配置自定義realm
//創建自定義realm
@Bean
public Realm getRealm(){
  return new CustomerRealm();
}
5.編寫控制器跳轉至index.html
@Controller
public class IndexController {
    @RequestMapping("index")
    public String index(){
        System.out.println("跳轉至主頁");
        return "index";
    }
}

5

6.啓動springboot應用訪問index

6
注意:

  • 默認在配置好shiro環境後默認環境中沒有對項目中任何資源進行權限控制,所有現在項目中所有資源都可以通過路徑訪問

  • 7.加入權限控制
  • 修改ShiroFilterFactoryBean配置

    //注入安全管理器
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    Map<String,String> map =  new LinkedHashMap<>();
    map.put("/**","authc");
    //配置認證和授權規則
    shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    

    9

8.重啓項目訪問查看

10

6.4 常見過濾器

  • 注意: shiro提供和多個默認的過濾器,我們可以用這些過濾器來配置控制指定url的權限:
配置縮寫 對應的過濾器 功能
anon AnonymousFilter 指定url可以匿名訪問
authc FormAuthenticationFilter 指定url需要form表單登錄,默認會從請求中獲取usernamepassword,rememberMe等參數並嘗試登錄,如果登錄不了就會跳轉到loginUrl配置的路徑。我們也可以用這個過濾器做默認的登錄邏輯,但是一般都是我們自己在控制器寫登錄邏輯的,自己寫的話出錯返回的信息都可以定製嘛。
authcBasic BasicHttpAuthenticationFilter 指定url需要basic登錄
logout LogoutFilter 登出過濾器,配置指定url就可以實現退出功能,非常方便
noSessionCreation NoSessionCreationFilter 禁止創建會話
perms PermissionsAuthorizationFilter 需要指定權限才能訪問
port PortFilter 需要指定端口才能訪問
rest HttpMethodPermissionFilter 將http請求方法轉化成相應的動詞來構造一個權限字符串,這個感覺意義不大,有興趣自己看源碼的註釋
roles RolesAuthorizationFilter 需要指定角色才能訪問
ssl SslFilter 需要https請求才能訪問
user UserFilter 需要已登錄或“記住我”的用戶才能訪問

6.5 認證實現

1. 在login.jsp中開發認證界面

28

<form action="${pageContext.request.contextPath}/user/login" method="post">
  用戶名:<input type="text" name="username" > <br/>
  密碼  : <input type="text" name="password"> <br>
  <input type="submit" value="登錄">
</form>
2. 開發controller
@Controller
@RequestMapping("user")
public class UserController {
  /**
    * 用來處理身份認證
    * @param username
    * @param password
    * @return
    */
  @RequestMapping("login")
  public String login(String username,String password){
    //獲取主體對象
    Subject subject = SecurityUtils.getSubject();
    try {
      subject.login(new UsernamePasswordToken(username,password));
      return  "redirect:/index.jsp";
    } catch (UnknownAccountException e) {
      e.printStackTrace();
      System.out.println("用戶名錯誤!");
    }catch (IncorrectCredentialsException e){
      e.printStackTrace();
      System.out.println("密碼錯誤!");
    }
    return "redirect:/login.jsp";
  }
}
  • 在認證過程中使用subject.login進行認證
3.開發realm中返回靜態數據(未連接數據庫)
@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("==========================");
        String principal = (String) token.getPrincipal();
        if("xiaochen".equals(principal)){
            return new SimpleAuthenticationInfo(principal,"123",this.getName());
        }
        return null;
    }
}
4.啓動項目以realm中定義靜態數據進行認證

27

  • 認證功能沒有md5和隨機鹽的認證就實現啦

6.6 退出認證

1.開發頁面退出連接
2.開發controller
@Controller
@RequestMapping("user")
public class UserController {
  /**
    * 退出登錄
    *
    */
  @RequestMapping("logout")
  public String logout(){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();//退出用戶
    return "redirect:/login.jsp";
  }
}
3.修改退出連接訪問退出路徑

25

4.退出之後訪問受限資源立即返回認證界面

26

6.7 MD5、Salt的認證實現

1.開發數據庫註冊

0.開發註冊界面
<h1>用戶註冊</h1>
<form action="${pageContext.request.contextPath}/user/register" method="post">
  用戶名:<input type="text" name="username" > <br/>
  密碼  : <input type="text" name="password"> <br>
  <input type="submit" value="立即註冊">
</form>
1.創建數據表結構
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) DEFAULT NULL,
  `password` varchar(40) DEFAULT NULL,
  `salt` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;
2.項目引入依賴
<!--mybatis相關依賴-->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.2</version>
</dependency>

<!--mysql-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.38</version>
</dependency>


<!--druid-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.19</version>
</dependency>
3.配置application.properties配置文件
server.port=8888
server.servlet.context-path=/shiro
spring.application.name=shiro

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
#新增配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root


mybatis.type-aliases-package=com.baizhi.springboot_jsp_shiro.entity
mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml

4.創建entity
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String  id;
    private String username;
    private String password;
    private String salt;
}
5.創建DAO接口
@Mapper
public interface UserDAO {
    void save(User user);
}
6.開發mapper配置文件
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
  insert into t_user values(#{id},#{username},#{password},#{salt})
</insert>
7.開發service接口
public interface UserService {
    //註冊用戶方法
    void register(User user);
}
8.創建salt工具類
public class SaltUtils {
    /**
     * 生成salt的靜態方法
     * @param n
     * @return
     */
    public static String getSalt(int n){
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            char aChar = chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }
        return sb.toString();
    }
}
9.開發service實現類
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDAO userDAO;

    @Override
    public void register(User user) {
        //處理業務調用dao
        //1.生成隨機鹽
        String salt = SaltUtils.getSalt(8);
        //2.將隨機鹽保存到數據
        user.setSalt(salt);
        //3.明文密碼進行md5 + salt + hash散列
        Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
        user.setPassword(md5Hash.toHex());
        userDAO.save(user);
    }
}
10.開發Controller
@Controller
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 用戶註冊
     */
    @RequestMapping("register")
    public String register(User user) {
        try {
            userService.register(user);
            return "redirect:/login.jsp";
        }catch (Exception e){
            e.printStackTrace();
            return "redirect:/register.jsp";
        }
    }
}
11.啓動項目進行註冊

24


2.開發數據庫認證

0.開發DAO
@Mapper
public interface UserDAO {

    void save(User user);
		//根據身份信息認證的方法
    User findByUserName(String username);
}
1.開發mapper配置文件
<select id="findByUserName" parameterType="String" resultType="User">
  select id,username,password,salt from t_user
  where username = #{username}
</select>
2.開發Service接口
public interface UserService {
    //註冊用戶方法
    void register(User user);
    //根據用戶名查詢業務的方法
    User findByUserName(String username);
}
3.開發Service實現類
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;
    @Override
    public User findByUserName(String username) {
        return userDAO.findByUserName(username);
    }
}
4.開發在工廠中獲取bean對象的工具類
@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }


    //根據bean名字獲取工廠中指定bean 對象
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}
5.修改自定義realm
 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("==========================");

        //根據身份信息
        String principal = (String) token.getPrincipal();
        //在工廠中獲取service對象
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
				//根據身份信息查詢
        User user = userService.findByUserName(principal);

        if(!ObjectUtils.isEmpty(user)){
            //返回數據庫信息
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), 
                                               ByteSource.Util.bytes(user.getSalt()),this.getName());
        }
        return null;
    }
6.修改ShiroConfig中realm使用憑證匹配器以及hash散列
@Bean
public Realm getRealm(){
  CustomerRealm customerRealm = new CustomerRealm();
  //設置hashed憑證匹配器
  HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
  //設置md5加密
  credentialsMatcher.setHashAlgorithmName("md5");
  //設置散列次數
  credentialsMatcher.setHashIterations(1024);
  customerRealm.setCredentialsMatcher(credentialsMatcher);
  return customerRealm;
}

23

6.8 授權實現

0.頁面資源授權
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

<shiro:hasAnyRoles name="user,admin">
        <li><a href="">用戶管理</a>
            <ul>
                <shiro:hasPermission name="user:add:*">
                <li><a href="">添加</a></li>
                </shiro:hasPermission>
                <shiro:hasPermission name="user:delete:*">
                    <li><a href="">刪除</a></li>
                </shiro:hasPermission>
                <shiro:hasPermission name="user:update:*">
                    <li><a href="">修改</a></li>
                </shiro:hasPermission>
                <shiro:hasPermission name="user:find:*">
                    <li><a href="">查詢</a></li>
                </shiro:hasPermission>
            </ul>
        </li>
        </shiro:hasAnyRoles>
        <shiro:hasRole name="admin">
            <li><a href="">商品管理</a></li>
            <li><a href="">訂單管理</a></li>
            <li><a href="">物流管理</a></li>
        </shiro:hasRole>
1.代碼方式授權
@RequestMapping("save")
public String save(){
  System.out.println("進入方法");
  //獲取主體對象
  Subject subject = SecurityUtils.getSubject();
  //代碼方式
  if (subject.hasRole("admin")) {
    System.out.println("保存訂單!");
  }else{
    System.out.println("無權訪問!");
  }
  //基於權限字符串
  //....
  return "redirect:/index.jsp";
}

22

2.方法調用授權
  • @RequiresRoles 用來基於角色進行授權
  • @RequiresPermissions 用來基於權限進行授權
@RequiresRoles(value={"admin","user"})//用來判斷角色  同時具有 admin user
@RequiresPermissions("user:update:01") //用來判斷權限字符串
@RequestMapping("save")
public String save(){
  System.out.println("進入方法");
  return "redirect:/index.jsp";
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5aivlkK0-1591145976799)(Shiro 實戰教程.assets/image-20200527203415114.png)]


3.授權數據持久化

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fu4xzEf1-1591145976800)(Shiro 實戰教程.assets/image-20200527204839080.png)]

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_pers`;
CREATE TABLE `t_pers` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `name` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (
  `id` int(6) NOT NULL,
  `roleid` int(6) DEFAULT NULL,
  `permsid` int(6) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) DEFAULT NULL,
  `password` varchar(40) DEFAULT NULL,
  `salt` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
  `id` int(6) NOT NULL,
  `userid` int(6) DEFAULT NULL,
  `roleid` int(6) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;


4.創建dao方法
 //根據用戶名查詢所有角色
User findRolesByUserName(String username);
//根據角色id查詢權限集合
List<Perms> findPermsByRoleId(String id);
5.mapper實現
<resultMap id="userMap" type="User">
  <id column="uid" property="id"/>
  <result column="username" property="username"/>
  <!--角色信息-->
  <collection property="roles" javaType="list" ofType="Role">
    <id column="id" property="id"/>
    <result column="rname" property="name"/>
  </collection>
</resultMap>

<select id="findRolesByUserName" parameterType="String" resultMap="userMap">
  SELECT u.id uid,u.username,r.id,r.NAME rname
  FROM t_user u
  LEFT JOIN t_user_role ur
  ON u.id=ur.userid
  LEFT JOIN t_role r
  ON ur.roleid=r.id
  WHERE u.username=#{username}
</select>

<select id="findPermsByRoleId" parameterType="String" resultType="Perms">
  SELECT p.id,p.NAME,p.url,r.NAME
  FROM t_role r
  LEFT JOIN t_role_perms rp
  ON r.id=rp.roleid
  LEFT JOIN t_perms p ON rp.permsid=p.id
  WHERE r.id=#{id}
</select>
6.Service接口
//根據用戶名查詢所有角色
User findRolesByUserName(String username);
//根據角色id查詢權限集合
List<Perms> findPermsByRoleId(String id);
7.Service實現
@Override
public List<Perms> findPermsByRoleId(String id) {
  return userDAO.findPermsByRoleId(id);
}

@Override
public User findRolesByUserName(String username) {
  return userDAO.findRolesByUserName(username);
}
8.修改自定義realm
public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取身份信息
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("調用授權驗證: "+primaryPrincipal);
        //根據主身份信息獲取角色 和 權限信息
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        User user = userService.findRolesByUserName(primaryPrincipal);
        //授權角色信息
        if(!CollectionUtils.isEmpty(user.getRoles())){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(role->{
                simpleAuthorizationInfo.addRole(role.getName());
                //權限信息
                List<Perms> perms = userService.findPermsByRoleId(role.getId());
                if(!CollectionUtils.isEmpty(perms)){
                    perms.forEach(perm->{
                        simpleAuthorizationInfo.addStringPermission(perm.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }
}

19

9.啓動測試

6.9 使用CacheManager

1.Cache 作用

  • Cache 緩存: 計算機內存中一段數據
  • 作用: 用來減輕DB的訪問壓力,從而提高系統的查詢效率
  • 流程:

18

2.使用shiro中默認EhCache實現緩存

1.引入依賴
<!--引入shiro和ehcache-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-ehcache</artifactId>
  <version>1.5.3</version>
</dependency>
2.開啓緩存
//3.創建自定義realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        //修改憑證校驗匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //設置加密算法爲md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //設置散列次數
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);

        //開啓緩存管理器
        customerRealm.setCachingEnabled(true);
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setCacheManager(new EhCacheManager());
        return customerRealm;
    }

17

3.啓動刷新頁面進行測試
  • 注意:如果控制檯沒有任何sql展示說明緩存已經開啓

3.shiro中使用Redis作爲緩存實現

1.引入redis依賴
<!--redis整合springboot-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置redis連接
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=0
3.啓動redis服務
➜  bin ls
dump.rdb        redis-check-aof redis-cli       redis-server    redis.conf
redis-benchmark redis-check-rdb redis-sentinel  redis-trib.rb
➜  bin ./redis-server redis.conf

16

4.開發RedisCacheManager
public class RedisCacheManager implements CacheManager {
    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
        System.out.println("緩存名稱: "+cacheName);
        return new RedisCache<K,V>(cacheName);
    }
}
5.開RedisCache實現
public class RedisCache<K,V> implements Cache<K,V> {

    private String cacheName;

    public RedisCache() {
    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public V get(K k) throws CacheException {
        System.out.println("獲取緩存:"+ k);
        return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public V put(K k, V v) throws CacheException {
        System.out.println("設置緩存key: "+k+" value:"+v);
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }

    @Override
    public V remove(K k) throws CacheException {
        return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public v remove(k k) throws CacheException {
        return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }


    //封裝獲取redisTemplate
    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
6.啓動項目測試發現報錯

15

  • 錯誤解釋: 由於shiro中提供的simpleByteSource實現沒有實現序列化,所有在認證時出現錯誤信息

  • 解決方案: 需要自動salt實現序列化

    • 自定義salt實現序列化

      //自定義salt實現  實現序列化接口
      public class MyByteSource extends SimpleByteSource implements Serializable {
          public MyByteSource(String string) {
              super(string);
          }
      }
      
    • 在realm中使用自定義salt

       @Override
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("==========================");
        //根據身份信息
        String principal = (String) token.getPrincipal();
        //在工廠中獲取service對象
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        User user = userService.findByUserName(principal);
        if(!ObjectUtils.isEmpty(user)){
          return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), 
                                            new MyByteSource(user.getSalt()),this.getName());
        }
        return null;
      }
      

      14

7.再次啓動測試,發現可以成功放入redis緩存

13


4. 加入驗證碼驗證

0.開發頁面加入驗證碼
  • 開發控制器

    @RequestMapping("getImage")
    public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
      //生成驗證碼
      String code = VerifyCodeUtils.generateVerifyCode(4);
      //驗證碼放入session
      session.setAttribute("code",code);
      //驗證碼存入圖片
      ServletOutputStream os = response.getOutputStream();
      response.setContentType("image/png");
      VerifyCodeUtils.outputImage(220,60,os,code);
    }
    
  • 放行驗證碼

    12

  • 開發頁面

    11

  • 修改認證流程

    @RequestMapping("login")
        public String login(String username, String password,String code,HttpSession session) {
            //比較驗證碼
            String codes = (String) session.getAttribute("code");
            try {
                if (codes.equalsIgnoreCase(code)){
                    //獲取主體對象
                    Subject subject = SecurityUtils.getSubject();
                        subject.login(new UsernamePasswordToken(username, password));
                        return "redirect:/index.jsp";
                }else{
                    throw new RuntimeException("驗證碼錯誤!");
                }
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("用戶名錯誤!");
            } catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                System.out.println("密碼錯誤!");
            }catch (Exception e){
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
            return "redirect:/login.jsp";
        }
    
  • 修改salt不能序列化的問題

    //自定義salt實現  實現序列化接口
    public class MyByteSource implements ByteSource,Serializable {
    
        private  byte[] bytes;
        private String cachedHex;
        private String cachedBase64;
    
        //加入無參數構造方法實現序列化和反序列化
        public MyByteSource(){
    
        }
    
        public MyByteSource(byte[] bytes) {
            this.bytes = bytes;
        }
    
        public MyByteSource(char[] chars) {
            this.bytes = CodecSupport.toBytes(chars);
        }
    
        public MyByteSource(String string) {
            this.bytes = CodecSupport.toBytes(string);
        }
    
        public MyByteSource(ByteSource source) {
            this.bytes = source.getBytes();
        }
    
        public MyByteSource(File file) {
            this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
        }
    
        public MyByteSource(InputStream stream) {
            this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
        }
    
        public static boolean isCompatible(Object o) {
            return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
        }
    
        public byte[] getBytes() {
            return this.bytes;
        }
    
        public boolean isEmpty() {
            return this.bytes == null || this.bytes.length == 0;
        }
    
        public String toHex() {
            if (this.cachedHex == null) {
                this.cachedHex = Hex.encodeToString(this.getBytes());
            }
    
            return this.cachedHex;
        }
    
        public String toBase64() {
            if (this.cachedBase64 == null) {
                this.cachedBase64 = Base64.encodeToString(this.getBytes());
            }
    
            return this.cachedBase64;
        }
    
        public String toString() {
            return this.toBase64();
        }
    
        public int hashCode() {
            return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
        }
    
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (o instanceof ByteSource) {
                ByteSource bs = (ByteSource)o;
                return Arrays.equals(this.getBytes(), bs.getBytes());
            } else {
                return false;
            }
        }
    
        private static final class BytesHelper extends CodecSupport {
            private BytesHelper() {
            }
    
            public byte[] getBytes(File file) {
                return this.toBytes(file);
            }
    
            public byte[] getBytes(InputStream stream) {
                return this.toBytes(stream);
            }
        }
    }
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章