一般来说,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>
2. 编写数据库查询该用户的服务类,以便授权调用

注意:

  • 角色授权:授权代码需要加ROLE_(ROLE_ADMIN,ROLE_USER),即数据库角色表字段对应的值需要有ROLE_前缀,controller上使用时不要加前缀。
  • 权限授权:设置和使用时,名称保持一至即可。

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. 编写配置类

@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的整合

<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>
2. 自定义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,"");
    }
}
3. 配置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();
    }
}
4. 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";
        }
 }
5. 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">
  </shiro:lacksRole>
  如果当前Subject没有角色将显示body体内容。

hasPermission标签
  <shiro:hasPermission name="user:admin">
  </shiro:hasPermission>
  如果当前Subject有权限将显示body体内容

评论