SpringBoot整合之SpringSecurity

本文代码示例已放入github:请点击我

快速导航------>src.main.java.yq.SpringSecurity

SpringSecurity是什么?

答:Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架

为什么使用SpringSecurity?

答:它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。而且对权限控制比较灵活。说白了,就是可以与Spring无缝对接且灵活,功能强大。

SpringSecurity缺点?

答:缺点也很明显,首先基于Spring,也就是离开了Spring就,咳咳,这个倒是不影响,目前大多数开发还是跟Spring离不开的,而且相对来说学习成本比较大,新手入门比较难。

SpringSecurity与ApacheShiro谁好?

答:没有谁好谁差,都是主流的权限管理框架,SpringSecurity比ApacheShiro权限控制更灵活,ApacheShiro比SpringSecurity使用入门更简单,都可以达到企业级的web权限控制,谁用牛了都厉害,所以不存在说谁厉害,具体还是看业务场景进行技术选型。但是作为程序猿,还是可以都掌握的。

怎么使用SpringSecurity?

首先我去扣了一个比较简单的SpringSecurity执行的流程图:

开始之前我先说一下我这个Dome的执行流程,也就是简单的基于Token登录的模式

也就是用户登录之后会返回一个loginToken,这个loginToken在后续的请求都会带上它,才允许访问哪些接口,如果没有这个token,就会提示没有权限访问。

那么接下来就是用SpringBoot正式开始整合SpringSecurity

1.首先加入POM文件(部分核心依赖)

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.1.6.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

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

2.创建SpringSecurity核心配置类

//lombok 注解 简化log日志的
@Slf4j
//不使用默认的SpringSecurity 使用我们自定义的
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class AuthorizationConfiguration extends WebSecurityConfigurerAdapter {

    private static final String key = "key";

    //进行权限认证的filter过滤器
    @Autowired
    private AuthFilter authFilter;

    //数据源
    @Autowired
    private DataSource datasource;

    //
    @Autowired
    private BaseApiService baseApiService;


    @Autowired
    private DIYAuthenticationEntryPoint diyAuthenticationEntryPoint;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //密码加密
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //用户身份验证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.
                //自定义的验证
                        userDetailsService(userDetailsServiceImpl())
                .and()
                .jdbcAuthentication()
                //密码加密方式
                .passwordEncoder(bCryptPasswordEncoder())
                .dataSource(datasource)
                .and()
                .authenticationProvider(new RememberMeAuthenticationProvider(key));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //跨域支持
                .cors()
                .and()
                //这个一定要关闭 不然的话使用post的请求会把报错 好像不关闭只能使用get请求
                .csrf().disable()
                //配置认证失败处理
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler())
                .and()
                //session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //开始授权的地方
                .authorizeRequests()
                //这些url可以不需要认证 直接访问
                .antMatchers("/static/**","/userLogin","/error","/saveTest").permitAll()
                //这个接口需要user权限
                .antMatchers("/api/testTwo").hasAuthority("user")
                //这个接口需要 admin权限
                .antMatchers("/api/**").hasAuthority("admin")
//                .anyRequest().hasAuthority("admin")
                .and()
                //在验证之前执行  也就是使用我们自己定义的filter过滤器进行用户验证
                .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
                //禁止页面缓存
                .headers().frameOptions().sameOrigin().cacheControl();
    }

    //没有授权的人(验证不通过
    public AuthenticationEntryPoint unauthorizedHandler() {
        // lambda
        return (request, response, authException) -> {
            log.error("auth error query path {}", request.getRequestURL());
            response.setContentType("application/json;charset=utf-8");
            //封装一个map返回页面
            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("data","null");
            objectObjectHashMap.put("message","您没有访问权限");
            objectObjectHashMap.put("rtnCode","403");
            response.getWriter().append(JSON.toJSONString(objectObjectHashMap));
        };
    }

    //跨域
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        //允许访问的请求类型
        configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
        //是否支持用户凭证
        configuration.setAllowCredentials(true);
        // setAllowedHeaders is important! Without it, OPTIONS preflight request
        // will fail with 403 Invalid CORS request
        //允许header中携带的内容  loginToken:这是我们登录的token的名称
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type", "loginToken"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //所有请求都使用 CorsConfiguration
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    //注入到容器  为什么需要在这里手动的注入 不适用注解 @Component等注入呢
    //因为在 UserDetailsServiceImpl 中有其他的依赖项 如果在使用 @Component 注入
    //那么SpringSecurity 还没有加载完成就会报错
    @Bean
    public UserDetailsServiceImpl userDetailsServiceImpl(){
        return new UserDetailsServiceImpl();
    }

    //同上原理
    @Bean
    public LoginTokenService loginTokenService() {
        return new LoginTokenService("key",userDetailsServiceImpl());
    }

}

3.开始创建配置类里面的依赖类等(认证filter过滤器)

//只执行一次 用于在用户登录验证之前进行验证
@Component
public class AuthFilter extends OncePerRequestFilter {


    //我们验证的授权token的key
    private final static String AUTHORIZATIONHEAD = "loginToken";

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private LoginTokenService loginTokenService;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //如果当前的用户没有身份令牌 表示没有登录
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //调用授权方法
            Authentication rememberMeAuth = authentication(request, response);
            if (rememberMeAuth != null) {
                // 如果授权成功
                try {
                    //进行授权验证
                    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
                    // 保存值
                    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                } catch (AuthenticationException authenticationException) {
                    logger.error("认证失败 ex{} ", authenticationException);
                }
            }
        }
        //开始下一个过滤器
        filterChain.doFilter(request, response);
    }

    //授权的方法
    public Authentication authentication(HttpServletRequest request,HttpServletResponse response){
        //得到请求中的 token
        String loginToken = request.getHeader(AUTHORIZATIONHEAD);
        if(StringUtils.isEmpty(loginToken)){
            return null;
        }
        //验证token
        return loginTokenService.check(loginToken,request,response);
    }

}

4.创建一个基于Json Web Token(jwt)的token类

//token服务 用于验证token和创建token
public class LoginTokenService extends TokenBasedRememberMeServices {

    public LoginTokenService(String key, UserDetailsService userDetailsService) {
        super(key, userDetailsService);
    }

    //过期时间 20周
    private final Long EXP_TIME = 1000L * TWO_WEEKS_S * 10;


    //token验证是否合法
    public Authentication check(String token, HttpServletRequest request, HttpServletResponse response) {
        try {
            //获得令牌
            String[] tokens = decodeCookie(token);
            //进行验证token 并且获得对象
            UserDetails userDetails = processAutoLoginCookie(tokens, request, response);
            return createSuccessfulAuthentication(request, userDetails);

        } catch (Exception e) {
            throw new IllegalArgumentException("错误的loginToken");
        }
    }


    //创建用户token
    public String creaetToken(String userName,String passWord){
        //过期时间
        Long exp = System.currentTimeMillis()+EXP_TIME;
        String tokenSignature = makeTokenSignature(exp, userName, passWord);
        return encodeCookie(new String[]{exp.toString(),userName,tokenSignature});
    }

}

5.创建一个可以得到用户身份认证信息的类

//可以用于得到登录用户的信息 用于用户身份认证
public class SecurityUserDetails implements UserDetails {

    //这是保存的主体信息
    private Test test;
    //所有的权限
    private List<GrantedAuthority> authorities;

    public SecurityUserDetails(Test test,List<GrantedAuthority> authorities){
        this.test = test;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return test.getPassWord();
    }

    @Override
    public String getUsername() {
        return test.getId().toString();
    }

    public Test getTest(){
        return test;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

6.创建一个用户认证的类,用于用户身份认证(登录)

//用户信息验证
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private MySqlService mySqlService;

    @Autowired
    private MySqlMapper mySqlMapper;

    //认证和授权
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        long id = Long.parseLong(username);
        List<GrantedAuthority> authorities = new ArrayList<>();
        Test testById = mySqlService.getTestById(id);
            if(testById == null){
            throw new UsernameNotFoundException("用户不能为空");
        }
        //权限赋值
        authorities.add(new SimpleGrantedAuthority("admin"));
            //这里就是我们刚刚那个类
        return new SecurityUserDetails(testById,authorities);
    }
}

7.创建一个Controller进行使用SpringSecurity登录

@RestController
public class UserController extends BaseApiService {

    //我的数据库访问层
    @Autowired
    private MySqlService mySqlService;

    //用于生产token和验证token的
    @Autowired
    private LoginTokenService loginTokenService;

    //SpringSecurity登录授权
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @PostMapping(value = "/userLogin")
    public ResponseBase login(@RequestBody Test test){
        Test testById = mySqlService.getTestById(test.getId());
        if(testById == null){
            return setResultError("用户账号错误");
        }
        if(! testById.getPassWord().equals(test.getPassWord())){
            return setResultError("密码错误");
        }
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        userDetailsService.loadUserByUsername(testById.getId().toString());
        String loginToken = loginTokenService.creaetToken(testById.getId().toString(), testById.getPassWord());
        objectObjectHashMap.put("loginToken",loginToken);
        objectObjectHashMap.put("userName",testById.getUserName());
        return setResultSuccessData(JSON.toJSONString(objectObjectHashMap),"登陆成功");
    }

}

好了,到了这里基本上的Dome示例就完成了,如果还不会SpringSecurity的可以好好挖掘一下,另外我们这里的只是简单的单点登录功能,SpringSecurity所有的功能远不止如此,比如SpringSecurityOauth2等等

谢谢大家的观看

本文代码示例已放入github:请点击我

快速导航------>src.main.java.yq.SpringSecurity

发布了25 篇原创文章 · 获赞 9 · 访问量 3061

猜你喜欢

转载自blog.csdn.net/qq_40053836/article/details/98848757