zoukankan      html  css  js  c++  java
  • SpringSecurity(一)

    个人认为,在框架中,最难的就是Spring与鉴权框架。大部分框架,即便不知道原理,知道如何使用,也能完成日常的开发。而鉴权框架和Spring不同,他们并没有限定如何去使用,更多的,需要程序员自己的想法。

    我的文章不会写得很细,只会帮你完成一个可以运行的HelloWorld。

    Maven依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.13.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>cn.seaboot</groupId>
        <artifactId>security</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>security</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Greenwich.SR5</spring-cloud.version>
        </properties>
    
        <dependencies>
            <!--<dependency>-->
                <!--<groupId>org.springframework.cloud</groupId>-->
                <!--<artifactId>spring-cloud-starter-oauth2</artifactId>-->
            <!--</dependency>-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>

    Controller

    主要测试如何获取当前登录用户的信息

    package cn.seaboot.security.ctrl;
    
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Mr.css
     * @date 2020-05-06 15:06
     */
    @RestController
    public class HelloController {
    
      @GetMapping("/hello")
      public String hello() {
        //获取登录的账号
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println(principal.getClass());
        System.out.println(principal);
        return "hello";
      }
    }

    SecurityConfig 

    主要配置都包含在此接口中,按照自己的实际需求调整即可。

    package cn.seaboot.security.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * @author Mr.css
     * @date 2020-05-07 23:38
     */
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
      /**
       * 下面这两行配置表示在内存中配置了两个用户,分别是javaboy和lisi,密码都是123,并且赋予了admin权限
       * @param auth AuthenticationManagerBuilder
       */
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("javaboy")
            .roles("admin")
            .password("$2a$10$Wuts2iHTzQBmeRVKJ21oFuTsvOJ5ffsqpD3DRzNupKwn5Gy54LEpC")
            .and()
            .withUser("lisi")
            .roles("user")
            .password("$2a$10$gDCkllHpktQfHgwYWKW2T.JCgkUZcTZTLfDBhlJvTLO/BDSMeA2YS");
      }
    
      /**
       * 加密算法
       */
      @Bean
      PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
      }
    
      /**
       * URL角色权限配置,下列代码的意思是:访问路径hello,需要有admin角色
       *
       * @param http HttpSecurity
       */
      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/hello/**").hasRole("admin")
            .antMatchers("/hello").hasRole("admin")
            .anyRequest().authenticated()
            .and()
            .formLogin().and()
            .httpBasic();
      }
    
      /**
       * 白名单配置:直接过滤掉该地址,即该地址不走 Spring Security 过滤器链
       */
      @Override
      public void configure(WebSecurity web){
        web.ignoring().antMatchers("/vercode");
      }
    
      /**
       * 测试加密算法
       * @param args
       */
      public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123"));;
        System.out.println(new BCryptPasswordEncoder().matches("123", "$2a$10$gDCkllHpktQfHgwYWKW2T.JCgkUZcTZTLfDBhlJvTLO/BDSMeA2YS"));;
      }
    }

    URL权限配置的其它可选项:

    在configure函数中,已经展示了如何给Url配置权限,更多的配置如下:

    antMatchers(url).hasRole()
    antMatchers(url).access()

    hasRole([role]) 当前用户是否拥有指定角色。
    hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
    hasAuthority([auth]) 等同于hasRole
    hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
    Principle 代表当前用户的principle对象
    authentication 直接从SecurityContext获取的当前Authentication对象
    permitAll 总是返回true,表示允许所有的
    denyAll 总是返回false,表示拒绝所有的
    isAnonymous() 当前用户是否是一个匿名用户
    isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
    isAuthenticated() 表示当前用户是否已经登录认证成功了。
    isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

    测试

    访问Hello地址,就会自动跳转登录页面(Spring Security内置),用自己配置的账号即可登录。

    问题一

    思考问题:我们的用户肯定是配置在数据库里的,登录页面也必定是用自己的,上述代码肯定不满足我们日常使用,我们该怎么编写我们需要的代码?

    答:通过formLogin()可以配置我们自己的登录页面,表单提交路径,以及首页地址。

    配置如下:

     http
            .formLogin()
            .loginPage("/login.html")
            .failureUrl("/login.html?error=1")
            .defaultSuccessUrl("/index.html")
            .loginProcessingUrl("/user/login")
            .permitAll()
            .and()

    登录的接口如下:

    package org.springframework.security.core.userdetails;
    
    public interface UserDetailsService {
      UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
    }

    问题二:

    思考问题:上述的接口,只有1个参数 UserName,那么问题就来了,假设我们有2个参数怎么办?比如:验证码。

    答:可以添加一个登录前置拦截,先验证验证码的有效性,然后再走我们的正常流程。

    http.addFilterBefore(new xxxxxxFilter(), xxxxxxxFilter.class)

     

    SecurityConfig进阶

    根据上述问题,对代码进行调整

    package cn.seaboot.security.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    
    /**
     * @author Mr.css
     * @date 2020-05-07 23:38
     */
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
      /**
       * @param auth AuthenticationManagerBuilder
       */
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 设置自定义的userDetailsService
        auth.userDetailsService(new CustomUserDetailsService())
            .passwordEncoder(passwordEncoder());
      }
    
      /**
       * 加密算法
       */
      @Bean
      PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
      }
    
      /**
       * URL角色权限配置,访问路径hello,需要有admin角色
       *
       * @param http HttpSecurity
       */
      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(new BeforeLoginAuthenticationFilter("/user/login", "/login.html"), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/hello/**").hasRole("admin")
            .antMatchers("/hello").hasRole("admin")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")
            .failureUrl("/login.html?error=1")
            .defaultSuccessUrl("/index.html")
            .loginProcessingUrl("/user/login")
            .permitAll()
            .and()
            .httpBasic();
    
    
        //session管理
        //session失效后跳转到登录页面
        http.sessionManagement().invalidSessionUrl("/toLogin");
    
        //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线
        //http.sessionManagement().maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy());
    
        //单用户登录,如果有一个登录了,同一个用户在其他地方不能登录
        http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
    
        //默认的登录页面有一个用于安全验证的token,如果使用模版引擎,可以使用表达式获取,这里直接使用html,因此先禁用
        //    <input name="_csrf" type="hidden" value="d2ef6916-316b-4889-895c-07a2ca3759fc">
        //    <input  type = “hidden”  name = “${_csrf.parameterName}”  value = “${_csrf.token}” />
        http.csrf().disable();
      }
    
      /**
       * 白名单配置:直接过滤掉该地址,即该地址不走 Spring Security 过滤器链
       */
      @Override
      public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/vercode");
      }
    }

    模拟用户登录

    package cn.seaboot.security.config;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     *
     * @author Mr.css
     * @date 2020-05-08 0:02
     */
    public class CustomUserDetailsService implements UserDetailsService {
    
      @Override
      public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        // TODO: 查询账户
        // 这里并没有真正去查询数据库,而是允许任意账号登录,密码都是123,并且都是admin角色
        // GrantedAuthority直译是授予权限,与config中配置的hasRole有歧义,但是功能上其实是一样的
        // 与Shiro不同,在Security中,并没有区分角色和权限
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_admin");
        grantedAuthorities.add(grantedAuthority);
        return new org.springframework.security.core.userdetails.User(userName,"$2a$10$gDCkllHpktQfHgwYWKW2T.JCgkUZcTZTLfDBhlJvTLO/BDSMeA2YS", grantedAuthorities);
      }
    }

    模拟验证码校验

    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author Mr.css
     * @date 2020-05-10 1:14
     */
    public class BeforeLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
      private String servletPath;
    
      public BeforeLoginAuthenticationFilter(String servletPath, String failureUrl) {
        super(servletPath);
        this.servletPath = servletPath;
        setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));
      }
    
      @Override
      public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
        return null;
      }
    
      /**
       * 这里模拟客户端的验证码,只要验证码是test,即可通过校验
       */
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if(servletPath.equals(req.getServletPath()) && "POST".equalsIgnoreCase(req.getMethod())){
            if (!"test".equals(req.getParameter("token"))) {
            unsuccessfulAuthentication(req, (HttpServletResponse) response, new InsufficientAuthenticationException("输入的验证码不正确"));
            return;
          }
        }
        chain.doFilter(request, response);
      }
    }

    登录页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    自定义表单验证:
    <form action="/user/login" method="post">
      <br/>
      用户名:
      <input type="text" name="username" placeholder="name"><br/>
      密码:
      <input type="password" name="password" placeholder="password"><br/>
      <input type="text" name="token" value="test"><br/>
      <input name="submit" type="submit" value="提交">
    </form>
    </body>
    </html>

    一些其它配置:

    //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线
    //http.sessionManagement().maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy());

    //单用户登录,如果有一个登录了,同一个用户在其他地方不能登录
    http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);

    默认的登录过滤器:
    UsernamePasswordAuthenticationFilter

  • 相关阅读:
    (转)ReentrantLock与Synchronized同步区别
    (转)Java NIO框架
    (转)Apache Mina网络框架
    (转)java中Executor、ExecutorService、ThreadPoolExecutor介绍
    路由选择协议
    SYN攻击
    网络基础
    [翻译]在Django项目中添加谷歌统计(Google Analytics)
    初识Python-web框架的这两天
    June本地环境搭建
  • 原文地址:https://www.cnblogs.com/chenss15060100790/p/12926660.html
Copyright © 2011-2022 走看看