從零開始搭建springboot項目4:shiro框架+thymeleaf配置

 

shiro和spring security 都是安全框架,都可以授權認證。

對比來講spring security自定義能力更強點,shiro單純配置來說更復雜點.

不過我技術不精,還是使用我更熟悉的shiro來進行授權認證功能的實現

下圖是shiro的3層構造,而我們寫也是根據這三層構造來寫的,用戶->安全事務管理器->realm對象

 

1.依賴shiro包

還是在pom文件寫入

		<!--shiro整合spring-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring-boot-starter</artifactId>
			<version>1.5.1</version>
		</dependency>

 

2.創建config文件和Realm文件

config文件:

public class ShiroConfig {
    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //關聯SecurityManager
        bean.setSecurityManager(securityManager);
        return bean;
    }

    //DefaultwebSecurityManager 配置核心安全事務管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //關聯Realm
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    //創建realm對象(需要自定義)
    @Bean
    public MyRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialsMatcher){
        MyRealm myRealm=new MyRealm();
        myRealm.setCredentialsMatcher(credentialsMatcher);
        return myRealm;
    }

    //以上三層是shiro的三層結構
    //下面是附加的

    // 配置密碼比較器(密碼加密)
    @Bean(name="credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher() {
        //RetryLimitHashedCredentialsMatcher爲另外類的構造函數
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");//散列算法:這裏使用MD5算法;
        credentialsMatcher.setHashIterations(0);//散列的次數,比如散列兩次,相當於 md5(md5(""));
        return credentialsMatcher;
    }
}

Realm文件:

public class MyRealm extends AuthorizingRealm {
    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("執行授權!!!!!");
        return null;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("執行認證!!!!!");
        return null;
    }
}

 

3.完善config文件

config文件:

我們還可以在shiroFilterFactoryBean那一層加配置設置很多東西

    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //關聯SecurityManager 配置核心安全事務管理器
        bean.setSecurityManager(securityManager);
        //配置登錄的URL(未登錄的用戶訪問的頁面)
        //bean.setLoginUrl("/auth/login");
        // 配置登錄成功的url(登錄後用戶訪問的頁面)
        //bean.setSuccessUrl("/auth/index");

        //自定義攔截器
        Map<String,Filter> MyfiltersMap=new LinkedHashMap<String,Filter>();
        //限制同一個賬號的同時在線個數
        //MyfiltersMap.put("kickout",kickout)
        //配置訪問權限
        //<url,權限類型>
        Map<String,String> filterChainDefinitionMap=new LinkedHashMap<>();
        //注意過濾器配置順序 不能顛倒
        //配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了,登出後跳轉配置的loginUrl
        filterChainDefinitionMap.put("/auth/logout","logout");
        // 配置不會被攔截的鏈接 順序判斷
        //authc:所有url都必須認證通過纔可以訪問;
        // anon:所有url都都可以匿名訪問
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/img/**","anon");
        filterChainDefinitionMap.put("/auth/login","anon");
        filterChainDefinitionMap.put("/*", "authc");
        filterChainDefinitionMap.put("/**", "authc");//表示需要認證纔可以訪問
       //設置授權形式的訪問
        filterChainDefinitionMap.put("/sardine/**","perms[admin:play]");

        //設置未授權界面
        bean.setUnauthorizedUrl("/403");
        //配置shiro的主要攔截器(默認有個攔截器)
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

anon: 無需認證即可訪問
authc: 需要認證纔可訪問
user: 點擊“記住我”功能可訪問
perms: 擁有權限纔可以訪問
role: 擁有某個角色權限才能訪問

 

4.完善Realm文件之登錄驗證

前一章偷懶的,這裏補上,創建業務層,並且多加個login方法進行登錄驗證

public interface UsersService {
    List<Users> queryUserList();
    Users queryUserByUserName(String username);
    Users queryUserById(int id);
    ResponseBean login(String username,String password);
}
@Service
public class UsersServiceImpl implements UsersService{
    @Autowired
    private UsersMapper usersMapper;

    @Override
    public List<Users> queryUserList() {
        return usersMapper.queryUserList();
    }

    @Override
    public Users queryUserByUserName(String username) {
        return usersMapper.queryUserByUserName(username);
    }

    @Override
    public Users queryUserById(int id) {
        return usersMapper.queryUserById(id);
    }

    @Override
    public ResponseBean login(String username, String password) {
        //獲取當前用戶
        Subject subject= SecurityUtils.getSubject();
        //設置當前用戶的登錄數據
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //登錄認證  注意這裏的帳號密碼是跟Realm文件那邊設置的進行對比驗證
        try{
            subject.login(token);
        } catch (IncorrectCredentialsException ice) {
            // 捕獲密碼錯誤異常
            return new ResponseBean(ResponseCode.Fail.GetValue(),"密碼錯誤",null);
        } catch (UnknownAccountException uae) {
            // 捕獲未知用戶名異常
            return new ResponseBean(ResponseCode.Fail.GetValue(),"賬號異常",null);
        } catch (ExcessiveAttemptsException eae) {
            // 捕獲錯誤登錄過多的異常
            return new ResponseBean(ResponseCode.Fail.GetValue(),"錯誤次數過多,賬號被鎖定,請於10分鐘後再進行嘗試",null);
        }catch (LockedAccountException lae){
            return new ResponseBean(ResponseCode.Fail.GetValue(),"帳號未激活",null);
        }catch (AuthenticationException ae){
            return new ResponseBean(ResponseCode.Fail.GetValue(),"帳號不存在",null);
        }
        ResponseBean responseBean = null;
        Users user = queryUserByUserName(username);
        subject.getSession().setAttribute("user", user);
        SecurityUtils.getSubject().getSession().setTimeout(-1000l);
        return new ResponseBean(ResponseCode.Success.GetValue(),"登陸成功",user);
    }
}

 

然後在Realm文件中寫方法

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UsersService usersService;

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("執行授權!!!!!");
        return null;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("執行認證!!!!!");
        //這裏就獲取登錄時當前用戶設置的登錄信息
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //根據當前用戶的用戶名查找數據庫的密碼
        Users users=usersService.queryUserByUserName(token.getUsername());
        if(users!=null){
            //根據用戶名在數據庫找不到,重返login頁面
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("redirect:/login");
            modelAndView.addObject("msg","賬號不存在");
            //return null;
            throw new AuthenticationException("帳號不存在");
        }
        else{
            //根據用戶名在數據庫找到用戶了,那麼獲取密碼,並返回,進行比較
            return new SimpleAuthenticationInfo(users.getUsername(),users.getPassword(),this.getClass().getName());
        }
    }
}

至此,shiro的登錄認證功能基本已配置完。

 

Realm文件登錄驗證測試(導入thymeleaf):

到實際測試階段,這時,我選擇偷懶,所以先導入themleaf依賴,還是pom文件

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

然後繼續yml文件配置

spring下面加入

  #thymeleaf設置
  #模板編碼
  thymeleaf:
    encoding: UTF-8
    cache: false        #啓用模板緩存(開發時建議關閉)
    mode: HTML5          #應用於模板的模板模式ervlet
    servlet:
      content-type: text/html   #Content-Type值
    enabled: true        #啓用MVC Thymeleaf視圖分辨率
    prefix: classpath:/templates/    #在構建URL時預先查看名稱的前綴
    suffix: .html            #構建URL時附加查看名稱的後綴

整體:

spring:
  #熱部署
  devtools:
    restart:
      enabled: true
      additional-paths: src/main/java
  #數據庫配置
  datasource:
    #1.JDBC
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xx.xxx.xxx.xxx/sardines?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    username: root
    password: root
    druid:
      #2.連接池配置
      #初始化連接池的連接數量 大小,最小,最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      #配置獲取連接等待超時的時間
      max-wait: 60000
      #配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一個連接在池中最小生存的時間,單位是毫秒
      min-evictable-idle-time-millis: 30000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: true
      test-on-return: false
      # 是否緩存preparedStatement,也就是PSCache  官方建議MySQL下建議關閉   個人建議如果想用SQL防火牆 建議打開
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      # 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
      filter:
        stat:
          merge-sql: true
          slow-sql-millis: 5000
      #3.基礎監控配置
      web-stat-filter:
        enabled: true
        url-pattern: /*
        #設置不統計哪些URL
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        session-stat-enable: true
        session-stat-max-count: 100
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        reset-enable: true
        #設置監控頁面的登錄名和密碼
        login-username: admin
        login-password: 123456
        allow: 127.0.0.1
        #deny: 122.1.1.1
  #thymeleaf設置
  #模板編碼
  thymeleaf:
    encoding: UTF-8
    cache: false        #啓用模板緩存(開發時建議關閉)
    mode: HTML5          #應用於模板的模板模式ervlet
    servlet:
      content-type: text/html   #Content-Type值
    enabled: true        #啓用MVC Thymeleaf視圖分辨率
    prefix: classpath:/templates/    #在構建URL時預先查看名稱的前綴
    suffix: .html            #構建URL時附加查看名稱的後綴


#mybatis配置
mybatis:
  #設置基本包 # 注意:對應實體類的路徑
  type-aliases-package: com.sardine.myproject.pojo
  #告訴去哪找xml文件 #注意:一定要對應mapper映射xml文件的所在路徑
  mapper-locations: classpath:mybatis/mapper/*.xml
server:
  port: 8080

 

至此,shiro框架的用戶認證和thymeleaf配置就完成了。

實際使用

接下來,就是在前端頁面引用thymeleaf,然後後端寫代碼進行操作了,我暫時做了個簡單的登錄,下面是具體代碼。

ResponseCode文件和UserController文件:

public enum ResponseCode {
    Success(200),
    Fail(400),
    Error(404);

    private int value = 0;
    private ResponseCode(int value) {     //必須是private的,否則編譯錯誤
        this.value = value;
    }
    public int GetValue(){
        return  this.value;
    }

}
@Controller
public class UserController {
    @Autowired
    private UsersMapper usersMapper;
    @Autowired
    private UsersService usersService;

    @GetMapping({"/"})
    public String toLogin(){
         return "login";
    }

    @PostMapping("/login")
    public String login(String username,String password){
        System.out.println("username:"+username+"  password:"+password);
        ResponseBean responseBean= usersService.login(username,password);
        if(responseBean.getCode()== ResponseCode.Success.GetValue()){
            return "index";
        }
        else{
            System.out.println("登錄失敗:"+responseBean.getMsg());
            //System.out.println("登錄失敗");
            return "login";
        }
    }
}

UserMapper.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sardine.mapper.UsersMapper">
    <resultMap id="userResultMap" type="com.sardine.pojo.Users">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="username" jdbcType="VARCHAR" property="username"/>
        <result column="password" jdbcType="VARCHAR" property="password"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
    </resultMap>
    <sql id="BASE_TABLE">
        users a
    </sql>
    <sql id="BASE_COLUMN">
        a.*
    </sql>
    <select id="queryUserList" resultMap="userResultMap">
        SELECT
        <include refid="BASE_COLUMN"/>
        FROM
        <include refid="BASE_TABLE"/>
    </select>
    <select id="queryUserByUserName" resultMap="userResultMap" parameterType="String">
        SELECT
        <include refid="BASE_COLUMN"/>
        FROM
        <include refid="BASE_TABLE"/>
        <where>
            a.username = #{username}
        </where>
    </select>
    <select id="queryUserById" resultMap="userResultMap" parameterType="int">
        SELECT
        <include refid="BASE_COLUMN"/>
        FROM
        <include refid="BASE_TABLE"/>
        <where>
            id = #{id}
        </where>
    </select>

</mapper>

前端文件:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>測試</title>
</head>
<body>
    <h1>登錄</h1>
    <form method="post" th:action="@{/login}">
        <p>用戶名: <input type="text" name="username"></p>
        <p>密碼: <input type="text" name="password"></p>
        <p><input type="submit"></p>
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <h1>登錄成功</h1>
</body>
</html>

PS:這裏講幾個點:

1.themleaf要用<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">這句話必不可少

2.themleaf語法請自學

3.爲了統一規格我額外建了個enum類,嫌麻煩直接寫數字也可以

4.ReaponseBean是比較爛大街的自己寫的,返回code、msg、data這三個數據的一個類,這裏就不貼出來了。

5.由於我寫的是加密的密碼比較器,所以數據庫那邊的密碼不是123456,而是md5加密後的數據

密碼:123456 轉換成這裏寫的md5加密後的結果是:e10adc3949ba59abbe56e057f20f883e

 

 

5.用戶認證做完,繼續完善Realm文件的權限認證

修改Realm文件,這裏也就不寫實際應用了,無非就是能進去,不能進去,然後在config文件已經設置過授權頁面和未授權頁面的跳轉了。

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("執行授權!!!!!");
        SimpleAuthorizationInfo Info = new SimpleAuthorizationInfo();
        //獲取登錄後的用戶,如果用戶有admin角色或用戶的Principal信息保存的是sardine用戶名,那麼添加權限
        Subject subject= SecurityUtils.getSubject();
        if(subject.hasRole("admin")||subject.getPrincipal().equals("sardine")) {
            Info.addStringPermission("admin:play");
        }
        return Info;
    }

PS:這裏的addStringPermission裏面的參數,可以改成通過自動裝配註解,從數據庫取權限名稱。

在controller層也可以寫註解來限制請求

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