Shiro 實戰教程(下)

1

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

個人我的博客:html

天涯志

個人公衆號:菜鳥小謝java

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
注意:mysql

  • 默認在配置好shiro環境後默認環境中沒有對項目中任何資源進行權限控制,全部如今項目中全部資源均可以經過路徑訪問
  • 7.加入權限控制
  • 修改ShiroFilterFactoryBean配置redis

    //注入安全管理器
    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 MD五、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實現序列化spring

    • 自定義salt實現序列化sql

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

      @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

  • 修改認證流程apache

    @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);
            }
        }
    }
相關文章
相關標籤/搜索