Spring Boot集成Spring Security或Shiro實現用戶登錄認證和授權,thymeleaf與之整合。

一般來說,Web 應用的安全性包括用戶認證(Authentication)和用戶授權(Authorization)兩個部分。

  • 用戶認證:指的是驗證某個用戶是否爲系統中的合法主體,也就是說用戶能否訪問該系統。(登錄)
  • 用戶授權:指的是該登錄用戶是否有執行某個操作的權限。(用戶與管理員,遊客與商家)

集成SpringSecurity

  1. 在項目導入Spring Security的依賴。
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--引入Thymeleaf依賴-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-thymeleaf</artifactId>
   </dependency>
<!--Security與Thymeleaf整合-->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

注意:

  • 角色授權:授權代碼需要加ROLE_(ROLE_ADMIN,ROLE_USER),即數據庫角色表字段對應的值需要有ROLE_前綴,controller上使用時不要加前綴。
  • 權限授權:設置和使用時,名稱保持一至即可
  1. 編寫數據庫查詢該用戶的服務類,以便授權調用
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //以下兩個是分別獲取登陸用戶的角色和權限,可以選擇利用角色作爲授權,或者權限作爲授權
        Collection<GrantedAuthority> roleauthorities = new ArrayList<>();
        
       /* Collection<GrantedAuthority> pageauthorities = new ArrayList<>();*/
       
        //角色 數據庫獲取該用戶的角色名
        Set<role> roles = roleService.roles(username);
        
        //權限 數據庫獲取該用戶權限名
        /*List<user_Page> user_pages = userPageService.user_Page_List(username);*/

        //將用戶角色放入roleauthorities
        if(roles != null){
            for (role role : roles) {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRolename());
                roleauthorities.add(authority);
            }
        }

        //將用戶權限放入pageauthorities
       /* if (user_pages !=null && !user_pages.isEmpty()){
            for (user_Page userPage:user_pages){
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(userPage.getRoleCode());
                pageauthorities.add(authority);
            }
        }*/
        
        //根據角色授權還是根據權限授權自己選,此處,根據用戶角色授權
        UserDetails roleuserDetails = new User(login.getLoginusername(),bCryptPasswordEncoder.encode(login.getLoginpassword()),roleauthorities);
       /*  UserDetails pageuserDetails = new User(login.getLoginusername(),bCryptPasswordEncoder.encode(login.getLoginpassword()),pageauthorities);*/
        return roleuserDetails;
    }
}
  1. 編寫配置類
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   //定製請求的授權規則
   @Override
   protected void configure(HttpSecurity http) throws Exception {

       http.authorizeRequests().antMatchers("/").permitAll()
      .antMatchers("/user/**").hasAnyRole("ROLE_USER","ROLE_ADMIN")
      .antMatchers("/admin/**").hasRole("ROLE_ADMIN")
      /*.antMatchers("/user/**").hasAnyAuthority("user","admin")*/
      /*.antMatchers("/admin/**").hasAuthority("admin")*/
	/*hasIpAddress("127.0.0.1") 只有發送的Ip匹配時才允許*/

       //開啓自動配置的登錄功能:如果沒有權限,就會跳轉到登錄頁面!
       http.formLogin()
          .usernameParameter("username") //前端傳來的表單name格式
          .passwordParameter("password") //前端傳來的表單name格式
          .loginPage("/toLogin") //沒有登錄所跳轉到的登錄頁
          .loginProcessingUrl("/login"); // 登陸表單提交請求

       //開啓自動配置的註銷的功能
           // /logout 註銷請求
           // .logoutSuccessUrl("/"); 註銷成功來到首頁

       http.csrf().disable();//關閉csrf功能:跨站請求僞造,默認只能通過post方式提交logout請求
       http.logout().logoutSuccessUrl("/");// 註銷成功來到首頁

       //記住我
       http.rememberMe().rememberMeParameter("remember");
  }

   //定義認證規則
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {

       //spring security 官方推薦的是使用bcrypt加密方式,基於內存的驗證
       /*auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
              .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("ROLE_ADMIN","ROLE_USER")
              .and()
              .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("ROLE_USER");*/

	     //基於數據庫的認證
	       auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
  }
}
  1. 與thymeleaf整合
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
	

<div sec:authorize="!isAuthenticated()>
 //沒有登錄,就顯示。
</div>
					
<div sec:authorize="isAuthenticated()>
 //登錄後才顯示
	用戶名:<span sec:authentication="principal.username"></span>
    角色:<span sec:authentication="principal.authorities"></span>
</div>
	
	<div sec:authorize="hasRole('ROLE_ADMIN')">
		//admin角色纔看得見
	</div>
	<div sec:authorize="hasAnyRole('ROLE_ADMIN','ROLE_USER')">
		//admin和user角色纔看得見
	</div>
		<div sec:authorize="hasAuthority('admin')">
		//admin權限的用戶就能看到
	</div>
	<div sec:authorize="hasAnyAuthority('admin','user')">
		//admin和user權限的用戶就能看到
	</div>

集成Shiro

  1. 導入shiro
        <!--導入shiro安全框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.1</version>
        </dependency>
		<!--引入Thymeleaf依賴-->
	   <dependency>
		   <groupId>org.springframework.boot</groupId>
		   <artifactId>spring-boot-starter-thymeleaf</artifactId>
	   </dependency>
		  <!-- thymelealf與shiro的整合 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
  1. 自定義UserRealm類:用於查詢用戶的角色和權限信息並保存到權限管理器
public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserServiceimpl userServiceimpl; //數據庫操作的實現類

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //拿到當前登錄的用戶信息
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();
        /*for (Role role : currentUser.getRoles()) {
            //添加角色
            info.addRole(role.getRoleName());
            //添加權限
            for (Permissions permissions : role.getPermissions()) {
                info.addStringPermission(permissions.getPermissionsName());
            }
        }*/
        if(currentUser.getLevel() == 1) { //根據數據庫表對應該用戶存儲的等級進行權限授權
            info.addStringPermission("user:ordinary");
        }else if(currentUser.getLevel() == 2){
            info.addStringPermission("user:admin");
        }else if(currentUser.getLevel() == 3){
            info.addStringPermission("user:admin");
        }
        return info;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken userToken = (UsernamePasswordToken) token; //獲取控制層傳來的token信息。
        User user = userServiceimpl.queryByUnameOrEmail(userToken.getUsername());
        if (user == null) {//沒有這個用戶
            return null;//拋出UnknownAccountException的異常
        }
        String passwordMD5 = user.getPwdMD5(); //獲取該用戶數據庫用戶表中儲存的加密後的密碼。
        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession(); 
        session.setAttribute("loginUser",user); //將該用戶信息放入當前會話中
        //可以加密, MD5,MD5鹽值加密
        //密碼認證shiri自動做,已經加密
        ByteSource salt = ByteSource.Util.bytes(user.getUsername()); //MD5鹽值加密
        return new SimpleAuthenticationInfo(user, passwordMD5,salt,"");
    }
}
  1. 配置Shiro配置類
    把UserRealm和SecurityManager等加入到spring容器,順便配置cookie,session和密碼加密配置。
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        //設置安全管理器
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的內置過濾器
        /*
            anon:無需認證可以訪問
            authc:必須認證纔可以訪問
            user:必須擁有 記住我 功能才能用
            perms:擁有對某個資源的權限才能訪問
            role:擁有某個角色權限才能訪問
            logout:退出登錄
         */
        Map<String, String> filterMap = new LinkedMap();

        //權限操作
        filterMap.put("/login", "anon");
        filterMap.put("/logout", "authc");
        filterMap.put("/", "anon");
        filterMap.put("/function/*", "authc");
        filterMap.put("/home","authc,user");
        filterMap.put("/admin/*","authc,perms[user:admin]");
        filterMap.put("/user/*","authc,user");
        filterMap.put("/index", "authc,user");
        bean.setFilterChainDefinitionMap(filterMap);
        //設置登錄的請求
        bean.setLoginUrl("/login");
        //設置爲授權的請求
        bean.setUnauthorizedUrl("/noauth");
        return bean;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //關聯UserRealm
        securityManager.setRealm(userRealm);
        //關聯記住我功能
        securityManager.setRememberMeManager(rememberMeManager());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    //創建realm對象,自定義對象類
    @Bean(name = "userRealm")
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        // 告訴realm,使用credentialsMatcher加密算法類來驗證密文
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        userRealm.setCachingEnabled(false);
        return userRealm;
    }

    /**
     * cookie對象
     * @return
     */
    public SimpleCookie rememberMeCookie() {
        // 設置cookie名稱,對應登錄頁面的記住我radio的name
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        // 設置cookie的過期時間,單位爲秒,這裏爲7天
        cookie.setMaxAge(86400*7);
        return cookie;
    }

    /**
     * cookie管理對象
     * @return
     */
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // rememberMe cookie加密的密鑰
        cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }
    /**
     * shiro session的管理
     */
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //設置url重新setSessionIdUrlRewritingEnabled值爲false
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
    /**
     * 加密配置
     * @return
     */
    @Bean(name = "credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:這裏使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 散列的次數,比如散列兩次,相當於 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(1024);
        // storedCredentialsHexEncoded默認是true,此時用的是密碼加密用的是Hex編碼;false時用Base64編碼
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    //整合shiroDialect
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}
  1. controller層中對登錄的相關操作(部分代碼)
	@PostMapping("/toLogin")
    public String login(@RequestParam Map<String, Object> formData,
                        Model model,
                        RedirectAttributes redirectAttributes) {
       //獲取當前的用戶
        Subject subject = SecurityUtils.getSubject();
        //封裝當前用戶信息成token
	    UsernamePasswordToken token = new UsernamePasswordToken((String) formData.get("username"), (String) formData.get("password")); 
        //設置記住我
        if (formData.get("rememberMe") == "true") {
            token.setRememberMe(true);
        }
        try {
            subject.login(token);//執行登錄的方法。沒有異常就成功!
            userMapper.addUserView((String) formData.get("username"));
            return "redirect:/index"; //成功登錄~
        } catch (UnknownAccountException e) { //用戶名不存在
            model.addAttribute("msg", "用戶名或郵箱不存在!");
            model.addAttribute("success", false);
            return "login";
        } catch (IncorrectCredentialsException e) {//密碼錯誤
            model.addAttribute("msg", "密碼錯誤!");
            model.addAttribute("success", false);
            model.addAttribute("username",formData.get("username"));
            return "login";
        }
 }
  1. Thymeleaf與Shiro的整合操作
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<shiro:authenticated> //登錄認證後可看
    <a href="/1">1111</a>
</shiro>

<shiro:notAuthenticated> //未登錄認證可看,記住我的cookies的自動登錄也算。
    <a href="/1">1111</a>
</shiro>

<div shiro:hasPermission="user:admin"> //只有管理員權限能看見
    <a href="/1">1111</a>
</div>

<div shiro:hasPermission="user:ordinary"> //用戶權限可見
    <a href="/2">2222</a>
</div>
<div shiro:hasRole="user"> //用戶角色可以見
    <a href="/2">2222</a>
</div>

shiro標籤的相關說明:

guest標籤
  <shiro:guest>
   用戶沒有身份驗證時顯示相應信息,即遊客訪問信息。
  </shiro:guest>

user標籤
  <shiro:user>
    用戶已經身份驗證/記住我登錄後顯示相應的信息。
  </shiro:user>

authenticated標籤
  <shiro:authenticated>
   用戶已經身份驗證通過,即Subject.login登錄成功,不是記住我登錄的。
  </shiro:authenticated>


notAuthenticated標籤
  <shiro:notAuthenticated>
  
  </shiro:notAuthenticated>
  用戶已經身份驗證通過,即沒有調用Subject.login進行登錄,包括記住我自動登錄的也屬於未進行身份驗證。

principal標籤
  <shiro: principal/>
   相當於((User)Subject.getPrincipals()).getUsername()。
  <shiro:principal property="username"/>

lacksPermission標籤
  <shiro:lacksPermission name="org:create">
	如果當前Subject沒有權限將顯示body體內容。
  </shiro:lacksPermission>
  

hasRole標籤
  <shiro:hasRole name="admin">
  	如果當前Subject有角色將顯示body體內容。
  </shiro:hasRole>
 

hasAnyRoles標籤
  <shiro:hasAnyRoles name="admin,user">
    //如果當前Subject有任意一個角色(或的關係)將顯示body體內容。
  </shiro:hasAnyRoles>
 

lacksRole標籤
  <shiro:lacksRole name="abc">
   如果當前Subject沒有角色將顯示body體內容。
  </shiro:lacksRole>


hasPermission標籤
  <shiro:hasPermission name="user:admin">
   如果當前Subject有權限將顯示body體內容
  </shiro:hasPermission>

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