Spring Boot学习(十六):Spring Boot与Spring Security整合

这里是一个学习过程笔记的汇总:Spring Boot学习汇总


学习之前,需要对Spring Security有个大概了解,可以去看官方文档:Spring Security官方文档

首先创建一个Spring Boot项目,导入web、Security(这里导入的是5.1.2版本)以及JPA模块。整合JPA操作请看这篇:Spring Boot整合jpa相关操作

项目结构如下:

首先创建相关数据表:

// 用户表
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

// 角色表
CREATE TABLE `role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

// 用户和角色关联表
CREATE TABLE `user_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `u_id` bigint(20) NOT NULL,
  `r_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

// 权限表
CREATE TABLE `resource` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `re_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

// 角色和权限关联表
CREATE TABLE `role_resource` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `r_id` bigint(20) NOT NULL,
  `re_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

主配置文件application.yml中增加数据库连接等相关配置:

server:
  port: 8080
  servlet:
    context-path: /mysecurity   
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springboot_test?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: xxxxxxx
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update


创建相关的实体类:

package com.example.springbootsecuritytest.bean;

import javax.persistence.*;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String username;

    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
package com.example.springbootsecuritytest.bean;

import javax.persistence.*;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
@Entity
@Table(name = "role")
public class Role {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "role_name")
    private String roleName;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
}
package com.example.springbootsecuritytest.bean;

import javax.persistence.*;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
@Entity
@Table(name = "user_role")
public class UserRole {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "u_id")
    private Long uId;

    @Column(name = "r_id")
    private Long rId;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getuId() {
        return uId;
    }

    public void setuId(Long uId) {
        this.uId = uId;
    }

    public Long getrId() {
        return rId;
    }

    public void setrId(Long rId) {
        this.rId = rId;
    }
}
package com.example.springbootsecuritytest.bean;

import javax.persistence.*;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
@Entity
@Table(name = "resource")
public class Resource {

    @Id
    @GeneratedValue
    private Long id;

    private String url;

    @Column(name = "re_name")
    private String reName;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getReName() {
        return reName;
    }

    public void setReName(String reName) {
        this.reName = reName;
    }
}
package com.example.springbootsecuritytest.bean;

import javax.persistence.*;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
@Entity
@Table(name = "role_resource")
public class RoleResource {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "re_id")
    private Long reId;

    @Column(name = "r_id")
    private Long rId;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getReId() {
        return reId;
    }

    public void setReId(Long reId) {
        this.reId = reId;
    }

    public Long getrId() {
        return rId;
    }

    public void setrId(Long rId) {
        this.rId = rId;
    }

}

除了实体类,还创建要给类来实现UserDetails接口,并实现该接口的 getAuthorities()方法,封装用户所拥有的所有权限。

package com.example.springbootsecuritytest.bean;

import com.example.springbootsecuritytest.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

//一定要有一个类,实现UserDetails接口,供程序调用
public class UserDetailsImpl implements UserDetails {

    private String username;
    private String password;
    //包含着用户对应的所有Role,在使用时调用者给对象注入roles
    private List<Role> roles;

    @Autowired
    private RoleService roleService;

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    //无参构造
    public UserDetailsImpl() {
    }

    //用User构造
    public UserDetailsImpl(User user) {
        this.username = user.getUsername();
        this.password = user.getPassword();
    }

    //用User和List<Role>构造
    public UserDetailsImpl(User user, List<Role> roles) {
        this.username = user.getUsername();
        this.password = user.getPassword();
        this.roles = roles;
    }

    public List<Role> getRoles()
    {
        return roles;
    }

    @Override
    //返回用户所有角色的封装,一个Role对应一个GrantedAuthority
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for(Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    //判断账号是否已经过期,默认没有过期
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    //判断账号是否被锁定,默认没有锁定
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    //判断信用凭证是否过期,默认没有过期
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    //判断账号是否可用,默认可用
    public boolean isEnabled() {
        return true;
    }
}

创建相关的dao接口:

package com.example.springbootsecuritytest.dao;

import com.example.springbootsecuritytest.bean.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.query.Param;

import java.util.List;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
public interface UserDao extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {

    List<User> findByUsername(@Param("username") String username);
}
package com.example.springbootsecuritytest.dao;

import com.example.springbootsecuritytest.bean.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
public interface RoleDao extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> {

    // 根据用户名查找所有的权限
    @Query(value = "select r.* from role r, user_role ur where ur.r_id = r.id and ur.u_id = ?1", nativeQuery = true)
    List<Role> findRolesByUserId(@Param("userId") Long userId);

    // 根据权限id查找所有的角色
    @Query(value = "select r.* from role r, role_resource rr where rr.r_id = r.id and rr.re_id = ?1", nativeQuery = true)
    List<Role> findRolesByResourceId(@Param("resourceId") Long resourceId);
}
package com.example.springbootsecuritytest.dao;

import com.example.springbootsecuritytest.bean.Resource;
import com.example.springbootsecuritytest.bean.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
public interface ResourceDao extends JpaRepository<Resource, Long>, JpaSpecificationExecutor<Resource> {

    @Query("from Resource where url =:url")
    Resource getResourceByUrl(@Param("url") String url);

}
package com.example.springbootsecuritytest.dao;

import com.example.springbootsecuritytest.bean.RoleResource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
public interface RoleResourceDao extends JpaRepository<RoleResource, Long>, JpaSpecificationExecutor<RoleResource> {

}

相关的service:

package com.example.springbootsecuritytest.service;

import com.example.springbootsecuritytest.bean.User;
import com.example.springbootsecuritytest.bean.UserDetailsImpl;
import com.example.springbootsecuritytest.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 */
@Service
public class UserService implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private RoleService roleService;

    @Transactional
    public List<User> getAllUser(){
        return userDao.findAll();
    }

    @Transactional
    public List<User> getByUsername(String username){
        return userDao.findByUsername(username);
    }

    public void saveUser(User user) {
        userDao.save(user);
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        System.out.println("查找用户:" + s);
        User user = getByUsername(s).get(0);
        if(user == null){
            throw new UsernameNotFoundException("没有该用户");
        }
        System.out.println("查到用户; " + user);
        //查到User后将其封装为UserDetails的实现类的实例供程序调用
        //用该User和它对应的Role实体们构造UserDetails的实现类
        return new UserDetailsImpl(user, roleService.findRolesByUserId((user.getId())));
    }
}

这个UserService比较特殊,实现了UserDetailsService接口,实现了loadUserByUsername方法,返回结果是UserDetails,上面我们已经创建了UserDetailsImpl类来实现这个接口:当用户登录时,就会走这个loadUserByUsername方法,通过用户名,查找用户的相关信息并封装成一个UserDetails(包括用户名,密码,拥有的所有权限)。

接下来创建一个过滤器:

package com.example.springbootsecuritytest.component;

import com.example.springbootsecuritytest.bean.Resource;
import com.example.springbootsecuritytest.bean.Role;
import com.example.springbootsecuritytest.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 *  用户请求一个地址的时候,截获这个地址,返回访问该地址需要的所有权限
 */
@Component
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private ResourceService resourceService;

    /**
     * 接收用户请求的地址,返回访问该地址需要的所有权限
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //得到用户的请求地址,控制台输出一下

        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        System.out.println("用户请求的地址是:" + requestUrl);

        //如果是注册页面和登录页面就不需要权限
        if ("/index".equals(requestUrl) || "/register".equals(requestUrl)) {
            return null;
        }
        
        Resource resource = resourceService.getResourceByUrl(requestUrl);

        //如果没有匹配的url则说明大家都可以访问
        if(resource == null) {
            return SecurityConfig.createList("ROLE_LOGIN");
        }

        //将resource所需要到的roles按框架要求封装返回(ResourceService里面的getRoles方法是基于RoleRepository实现的)
        List<Role> roles = resourceService.getRoles(resource.getId());
        int size = roles.size();
        String[] values = new String[size];
        for (int i = 0; i < size; i++) {
            values[i] = roles.get(i).getRoleName();
        }
        return SecurityConfig.createList(values);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

这个过滤器中有个getAttributes()方法,当启动项目后,浏览器输入的每个请求地址,都会被拦截下来,若判断是自定义的不需要权限即可操作的页面路径,则直接访问,否则继续判断访问该路径所需要的角色。

再创建一个管理器:

package com.example.springbootsecuritytest.component;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Iterator;

/**
 * @author junxiang
 * @date 2018/12/18 0018
 * Security需要用到一个实现了AccessDecisionManager接口的类
 *
 * 类功能:这个类的作用是接收FilterInvocationSecurityMetadataSourceImpl类返回的访问当前url所需要的权限列表(decide方法的第三个参数),
 *         再结合当前用户的信息(decide方法的第一个参数),决定用户是否可以访问这个url。
 * 判断规则:用户只要匹配到目标url权限中的一个role就可以访问
 */
@Component
public class AccessDecisionManagerImpl implements AccessDecisionManager {

    /**
     *
     * @param authentication 当前用户的信息
     * @param o
     * @param collection url权限列表
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        // 迭代器遍历目标url的权限列表
        Iterator<ConfigAttribute> iterator = collection.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();

            String needRole = ca.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登录");
                } else
                    return;
            }

            //遍历当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }

        //执行到这里说明没有匹配到应有权限
        throw new AccessDeniedException("权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

这个管理器,主要是判断当前登陆用户有没有权限访问资源,主要是decide()方法,根据当前登陆用户所拥有的角色,和要访问资源的所需角色相对比,有一个符合即可访问,看上面的代码应该能看得懂的。

接下来是最重要的一个配置类:

package com.example.springbootsecuritytest.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author junxiang
 * @date 2018/12/19 0019
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userService;

    // 根据一个url请求,获得访问它所需要的roles权限
    @Autowired
    private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;

    // 接收一个用户的信息和访问一个url所需要的权限,判断该用户是否可以访问
    @Autowired
    private AccessDecisionManager accessDecisionManager;


    /**
     * 定义认证用户信息获取来源,密码校验规则等
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // 有以下几种形式,使用第3种
        //inMemoryAuthentication 从内存中获取
        //auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123123")).roles("USER");

        //jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构
        //usersByUsernameQuery 指定查询用户SQL
        //authoritiesByUsernameQuery 指定查询权限SQL
        //auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);

        //注入userDetailsService,需要实现userDetailsService接口
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    /**
     * 在这里配置哪些页面不需要认证
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/register");
    }

    /**
     * 定义安全策略
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()       //配置安全策略
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(accessDecisionManager);
                        return o;
                    }
                })
//                .antMatchers("/hello").hasAuthority("ADMIN")
                .and()
                .formLogin()
                .loginPage("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException, IOException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        StringBuffer sb = new StringBuffer();
                        sb.append("{\"status\":\"error\",\"msg\":\"");
                        if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
                            sb.append("用户名或密码输入错误,登录失败!");
                        } else if (e instanceof DisabledException) {
                            sb.append("账户被禁用,登录失败,请联系管理员!");
                        } else {
                            sb.append("登录失败!");
                        }
                        sb.append("\"}");
                        out.write(sb.toString());
                        out.flush();
                        out.close();
                    }
                })
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
//                        ObjectMapper objectMapper = new ObjectMapper();
                        String s = "{\"status\":\"success\",\"msg\":"  + "}";
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                .and()
                .logout()    // 退出
                .permitAll()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
                        out.flush();
                        out.close();
                    }
                });
    }
}

这个类是安全策略类,实现了WebSecurityConfigurerAdapter接口,有三个configure方法,

第一个configure:参数为AuthenticationManagerBuilder,这个方法主要用来定义用户认证时的信息获取来源(即我们在前面定义的UserService类的的loadUserByUsername()方法)以及密码校验规则(采用的是Spring Security特有的BCryptPasswordEncoder加密);

第二个configure:用来配置哪些页面不要认证,比如注册等。

第三个configure:定义安全策略,配置上前面创建的过滤器以及管理器,还有登陆请求的路径,默认是"/login",以及默认的用户名密码参数名称,还有登陆成功、登陆失败以及权限不够时的处理器,这些处理器可以分别拿出来写成一个单独的类。还有配置退出登陆,即logout()方法。

再创建一个controller控制器:

package com.example.springbootsecuritytest.controller;

import com.example.springbootsecuritytest.bean.User;
import com.example.springbootsecuritytest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;


/**
 * @author junxiang
 * @date 2018/12/19 0019
 */
@Controller
public class SecurityController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/index")
    public String index() {
        System.out.println("进来登陆了...");
        return "myLoginPage";
    }

    @GetMapping(value = "/register")
    public String register() {
        System.out.println("开始注册...");
        return "register";
    }

    @GetMapping(value = "/product")
    public String product() {
        System.out.println("产品...");
        return "product";
    }

    /**
     * 注册
     * @param user
     */
    @PostMapping("/register-user")
    public String registerUser(User user) {
        String encodePassword = new BCryptPasswordEncoder().encode(user.getPassword().trim());
        user.setPassword(encodePassword);
        userService.saveUser(user);
        System.out.println("注册成功");
        return "registerSuccess";
    }
}

包含注册以及一些跳转请求,这个注册时输入的密码直接就使用BCryptPasswordEncoder进行加密再入库。

再写几个简单的h5页面:

myLoginPage.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div class="bg">
    <div class="contain">
        <div>
            <a class="title">登陆页</a>
        </div>
        <div>
            <form action="/mysecurity/login" method="post">
                <div class="username">
                    <input name="username" type="text" placeholder="输入账号">
                </div>
                <div class="password">
                    <input name="password" type="password" placeholder="输入密码">
                </div>
                <div>
                    <button type="submit" class="loginbutton">登录</button>
                </div>
            </form>
        </div>
    </div>
</div>
</div>
</body>

product.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<!--<head>-->
    <!--<link rel="stylesheet" href="css/login.css">-->
<!--</head>-->
<body>
<div class="bg">
    <div class="contain">
        <div>
            <a class="title">产品页</a>
        </div>
        <div>
            <h1>这是产品页面!!!我有权限访问了</h1>
        </div>
    </div>
</div>
</div>
</body>

register.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
    <div id='mask'>
        <div class="whiteBg">
            <div class="title">注册</div>
            <form action="/mysecurity/register-user" method="post">
                <div class="phoneBg">
                    <input class="registerInput" name="username" id="username" placeholder="请输入手机号" type="number">
                    <input id="inviter" type="hidden">
                </div>
                <div class="pwdBg">
                    <input class="registerInput" name="password" id="password" placeholder="请设置密码" type="password">
                </div>
                <div>
                    <button type="submit" class="loginbutton">注册</button>
                </div>
            </form>
        </div>
    </div>
</body>
</html>

registerSuccess.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div class="bg">
    <div class="contain">
        <div>
            <a class="title">注册成功</a>
        </div>
        <div>
            <h1>注册成功!!!</h1>
        </div>
    </div>
</div>
</div>
</body>

到此,整个案例的代码都贴出来了,

下面具体测试一下,先添加相关路径到resource表,如下:

role中设置两个角色,如下:

role和resource关联表增加数据:

即DBA角色没有访问product的权限,而ADMIN有。

启动项目,启动过程中会进行以下操作:

会先访问安全配置类的中的configure方法,定义用户认证信息的获取来源以及密码校验规则,

紧接着会继续访问安全配置类进行安全策略定义:

接下来会获取已配置的不需要认证的访问路径:

启动完成,请求注册页面: http://localhost:8080/mysecurity/register

我们已经在安全配置类中设置注册页面的请求接口不需要认证:

所以请求后直接跳转到注册页面:

注入用户名和密码点击注册,跳转到controller:

密码已经加密。

注册成功,给这个用户添加角色DBA,即没有访问product的权限,

再注册一个用户,分给他ADMIN的角色,即拥有访问product的权限。

两个用户:

用户权限关联表:

请求登陆页面:http://localhost:8080/mysecurity/index

可见已经配置了登陆页不需要权限即可访问,

使用拥有product访问权限的用户登陆:

登陆时会先走到定义的UserService类的loadUserByUsername(String s)方法

这个是调用的UserDetailsImpl类的构造函数进行返回,返回的结果就是用户的相关信息,包括用户名,密码,以及用户所拥有的角色,如下,进入到UserDetailsImpl类中getAuthorities()方法进行用户的角色封装

 完了后就进入到安全配置类中,登陆成功:

 

登陆成功后我们来访问product,这个用户是有权限的: http://localhost:8080/mysecurity/product

可见,访问product需要的角色是ADMIN。

再往下就到AccessDecisionManagerImpl类中进行角色匹配,若匹配上,访问product成功,若没有匹配上,返回权限不足。

 可见是匹配上了,看浏览器:

下面用另外一个用户登陆,登陆后访问product

可见用户所拥有的角色和访问资源所需的角色不匹配:

下面看一下如果没有登陆的情况下去访问product,会怎么弄?

没登录时直接访问,会直接跳转到login页面,让登陆。

好了,已上就是Spring Security的认证,权限操作,下次再研究下基于注解的权限控制。

发布了34 篇原创文章 · 获赞 43 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/pavel101/article/details/85321320