前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot(五)安全框架SpringSecurity和Shiro的集成

Spring Boot(五)安全框架SpringSecurity和Shiro的集成

作者头像
HcodeBlogger
发布2020-07-14 11:05:42
8640
发布2020-07-14 11:05:42
举报
文章被收录于专栏:Hcode网站Hcode网站

一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

  • 用户认证:指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。(登录)
  • 用户授权:指的是该登录用户是否有执行某个操作的权限。(用户与管理员,游客与商家)

集成SpringSecurity

  1. 在项目导入Spring Security的依赖。
代码语言:javascript
复制
<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>

2. 编写数据库查询该用户的服务类,以便授权调用

注意:

  • 角色授权:授权代码需要加ROLE_(ROLE_ADMIN,ROLE_USER),即数据库角色表字段对应的值需要有ROLE_前缀,controller上使用时不要加前缀。
  • 权限授权:设置和使用时,名称保持一至即可。
代码语言:javascript
复制
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;
    }
}

3. 编写配置类

代码语言:javascript
复制
@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());
  }
}

4. 与thymeleaf的整合

代码语言:javascript
复制
<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
代码语言:javascript
复制
        <!--导入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>

2. 自定义UserRealm类:用于查询用户的角色和权限信息并保存到权限管理器

代码语言:javascript
复制
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,"");
    }
}

3. 配置Shiro配置类 把UserRealm和SecurityManager等加入到spring容器,顺便配置cookie,session和密码加密配置。

代码语言:javascript
复制
@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();
    }
}

4. controller层中对登录的相关操作(部分代码)

代码语言:javascript
复制
    @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";
        }
 }

5. Thymeleaf与Shiro的整合操作

代码语言:javascript
复制
<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标签的相关说明:

代码语言:javascript
复制
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">
  </shiro:lacksRole>
  如果当前Subject没有角色将显示body体内容。

hasPermission标签
  <shiro:hasPermission name="user:admin">
  </shiro:hasPermission>
  如果当前Subject有权限将显示body体内容
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年6月29日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 集成SpringSecurity
  • 集成Shiro
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档