美文网首页
SpringSecurity

SpringSecurity

作者: blank_white | 来源:发表于2020-07-19 20:57 被阅读0次

    一、SpringSecurity 在一般Web工程下的使用(XML配置)

    需要的依赖

    
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-config</artifactId>
          <version>4.2.10.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-taglibs</artifactId>
          <version>4.2.10.RELEASE</version>
        </dependency>
    
    
    

    XML 配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:security="http://www.springframework.org/schema/security"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security.xsd">
    
    <!--    放行静态资源-->
        <security:http pattern="/css/**" security="none"/>
        <security:http pattern="/image/**" security="none"/>
        <security:http pattern="/js/**" security="none"/>
    
    <!--    是否自动加载 security 配置文件 -->
    <!--    是否使用 el 表达式-->
        <security:http auto-config="true" use-expressions="true">
    
    <!--        登录界面不许要权限-->
            <security:intercept-url pattern="/login2.jsp" access="permitAll()"/>
            <security:intercept-url pattern="/failure.jsp" access="permitAll()"/>
            <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" />
    
    <!--        配置认证信息-->
            <security:form-login login-page="/login2.jsp"
                                 login-processing-url="/login"
                                 default-target-url="/index.jsp"
                                 authentication-failure-url="/failure.jsp"/>
            <security:logout logout-url="/logout"
                             logout-success-url="/login2.jsp"/>
    <!--        是否禁止使用自带的 csrf -->
            <security:csrf disabled="true"/>
    <!--        开启 remember me 功能 的过滤器  配置有效期 配置数据库源(用于转换token)配置前端传参 name -->
            <security:remember-me token-validity-seconds="6000"
                                  data-source-ref="dataSource"
                                  remember-me-parameter="remember-me"/>
            <security:access-denied-handler error-page="/403.jsp"/>
        </security:http>
    
    
    <!--    手动添加账号密码提供者-->
    <!--    <security:authentication-manager>-->
    <!--        <security:authentication-provider>-->
    <!--            <security:user-service>-->
    <!--                &lt;!&ndash;                SpringSecurity 默认认为密码是加密过得 {noop} 表示后面是不加密的密码内容(5.1.5 版本,对于 4.x 版本 {noop} 并不起作用)&ndash;&gt;-->
    <!--                <security:user name="sysUser" password="{noop}sysUser" authorities="ROLE_USER"/>-->
    <!--                <security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>-->
    <!--            </security:user-service>-->
    <!--        </security:authentication-provider>-->
    <!--    </security:authentication-manager>-->
    
    <!--    加密类-->
        <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
        <security:authentication-manager>
    <!--        配置用户信息数据服务类-->
            <security:authentication-provider user-service-ref="userServiceImpl">
    <!--            配置加密类-->
                <security:password-encoder ref="passwordEncoder"/>
    
            </security:authentication-provider>
        </security:authentication-manager>
    
    
    <!--    springSecurity 权限注解开关 :secured-annotations="enabled"-->
    <!--    spring 权限注解开关 :pre-post-annotations="enabled"-->
    <!--    java250注解支持(java规范的) :jsr250-annotations="enabled"-->
        <security:global-method-security
                secured-annotations="enabled"
                pre-post-annotations="enabled"
                jsr250-annotations="enabled"/>
    
    </beans>
    

    自定义账号密码获取方式

    User 实现了 UserDetails 接口 ,通过 username 也就是登录 id,从数据库中获取到 user 信息,转换成 SpringSecurity 识别的 UserDetail 并返回,以便 Security 做后续对比

    import org.springframework.security.core.userdetails.User;
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        SysUserDao sysUserDao;
    
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
            SysUser sysUser = sysUserDao.selectUserByUserName(s);
            List<GrantedAuthority> list=new ArrayList<>();
            list.add(new SimpleGrantedAuthority("ROLE_USER"));
            User user = new User(sysUser.getUsername(), sysUser.getPassword(), list);
            return user;
        }
    }
    
    

    对应 xml 配置

    
        <security:authentication-manager>
            <security:authentication-provider user-service-ref="userServiceImpl">
            </security:authentication-provider>
        </security:authentication-manager>
    

    自定义加密方式

    在 xml 中配置 security:password-encoder ,这里使用的是 Security 提供的 BCryptPasswordEncoder

    如果自定义则自写类 实现 PasswordEncoder 接口,security 从前端获取的密码,会使用 PasswordEncoder.matches(CharSequence rawPassword, String encodedPassword) 方法 判断密码是否正确

    
        <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
        <security:authentication-manager>
    <!--        配置用户信息数据服务类-->
            <security:authentication-provider user-service-ref="userServiceImpl">
    <!--            配置加密类-->
                <security:password-encoder ref="passwordEncoder"/>
            </security:authentication-provider>
        </security:authentication-manager>
    

    remember me 功能

    前端登录提交时加入 remember-me

    <form name="f" action="/login" method="post">
        用户名:
        <input type="text" name="username"/>
        <br>
        密码:
        <input type="text" name="password"/>
        <br>
        <input type="checkbox" name="remember-me" value="true">
        <input type="submit">
        <security:csrfInput/>
    </form>
    

    security 配置文件

    <!--        开启 remember me 功能 的过滤器  配置有效期 配置数据库源(用于转换token)配置前端传参 name -->
        <security:http>
            <security:remember-me token-validity-seconds="6000"
                                  data-source-ref="dataSource"
                                  remember-me-parameter="remember-me"/>
        </security:http>
    

    记录 token 的表,必须按照这个表格建表,这是 security 要求的

    CREATE TABLE `persistent_logins` (
      `username` varchar(64) NOT NULL ,
      `series` varchar(64) NOT NULL ,
      `token` varchar(64) NOT NULL ,
      `last_used` timestamp NOT NULL ,
      PRIMARY KEY(`series`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    获取认证对象

    SecurityContextHolder : security 的工具类

    getContext() :获取 security 的容器

    getAuthentication() : 获取 认证对象

    SecurityContextHolder.getContext().getAuthentication().getName()
    

    获取用户名

    jsp 下可以使用动态标签库

    
    <%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
    
    你好: <%=SecurityContextHolder.getContext().getAuthentication().getName()%>  (- - )
    <br>
    你好2: <security:authentication property="principal.username"/>
    <br>
    nihao3: <security:authentication property="name"/>
    

    JSP 动态展示菜单标签

    如果没有对应角色,则不展示标签内的内容

    <%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
        <security:authorize access="hasAnyRole('ROLE_PRODUCT')">
            <a href="" >产品管理</a>
        </security:authorize>
    

    权限注解的使用

    1. 在配置文件中添加注解支持
        <!--    springSecurity 权限注解开关 :secured-annotations="enabled"-->
        <!--    spring 权限注解开关 :pre-post-annotations="enabled"-->
        <!--    java250注解支持(java规范的) :jsr250-annotations="enabled"-->    
        <security:global-method-security
                secured-annotations="enabled"
                pre-post-annotations="enabled"
                jsr250-annotations="enabled"/>
    

    需要注意的是:

    在 SpringMVC配置文件中 <context:component-scan base-package="com.nothing.controller"/>

    Controller 层的 bean,由 SpringMVC 的 IOC 容器管理,所以如果在 Controller 层使用 权限注解,注解支持的配置也应该写在 SpringMVC 的配置文件中

    同理: Service 层 是由 Spring 的 IOC 容器管理,如果在Service 层使用权限注解,注解支持的配置要在写在 spring 的配置文件中(如果Spring import 了 SpringSecurity 的配置文件,写在 SpringSecurity 的配置文件里也可以)

    1. 注解的使用

      • Secured : Security 权限注解
      • PreAuthorize: Spring 权限注解(spring 的 el 表达式注解)
      • RolesAllowed: Java规范权限注解(jsr250)
      // 可以在方法上使用
          //@Secured({"ROLE_USER"})
          @PreAuthorize("hasAnyRole('ROLE_XX')")
          //@RolesAllowed({"ROLE_XX"})
          @RequestMapping("word.do")
          public String sdas(){}
      
      // 也可以在 类上使用
      @RestController
      @Secured({"ROLE_XXX"})
      public class TestController {
      

      Java 规范权限注解需要添加依赖

          <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jsr250-api</artifactId>
            <version>1.0</version>
          </dependency>
      

    错误处理1

    1. 简单处理

      只能处理 403 异常

          </security:http>
              <security:access-denied-handler error-page="/403.jsp"/>
          </security:http>
      

    异常处理2

    老师讲的很好,看了两三遍。就是异常处理那点感觉有些问题,springSecurity不是基于一些filter来实现的嘛,但是@ControllerAdvice只能拦截到控制器的异常,filter中的异常时拦截不到的,所以还是建议用springSecurity内部的异常处理的机制,springSecurity会抛出两大类异常,一个是AuthenticationException(认证的异常),一个是AccessDeniedException(权限不足的异常),处理的方法时写两个类,一个继承AccessDeniedHandler接口,一个继承AuthenticationEntryPoint接口,然后在springSecurity的配置类configure(HttpSecurity httpSecurity)中配置刚刚写的这两个类,httpSecurity.exceptionHandling().accessDeniedHandler({实现了AccessDeniedHandler接口的类}).authenticationEntryPoint({实现了AuthenticationEntryPoint接口的类})

    二、SpringSecurity 整合 SpringBoot 集中式

    在SpringBoot 中使用 JSP

    1. 需要两个核心依赖
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
            </dependency>
    

    注意引入 taglib 否则 类似<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

    这样的标签无法引入使用

            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-taglibs</artifactId>
            </dependency>
    

    其他如果使用也需要引入

            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
            </dependency>
    
    1. 打包方式 需要改成 war

      <packaging>war</packaging>

    2. 启动方式要通过 maven 命令

      mvn spring-boot:run

      需要 Build 插件

          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
      
      

    SecurityConfig

    写如下配置类

    
    @Configuration
    @EnableWebSecurity
    // 权限注解启动
    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true,jsr250Enabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        // 自己实现这个借口,和 xml 配置法一样
        UserService userService;
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
    
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 内存加载账户密码防方式
            // auth.inMemoryAuthentication().withUser("user").password("{noop}123").roles("USER");
            // 使用 自定义 DetailService 
            auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 匹配 /login.jsp 放行
            // 匹配 /** 需要角色
            // 其他的任何请求 需要授权
            // 配置 登录 和 退出 ,登录页面 登录请求地址 等也需要 permitAll
            // 最后 不启用csrf
            http.authorizeRequests()
                    .antMatchers("/login2.jsp").permitAll()
                    .antMatchers("/**").hasAnyRole("USER","ADMIN")
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().loginPage("/login2.jsp").loginProcessingUrl("/login")
                    .defaultSuccessUrl("/index.jsp")
                    .failureForwardUrl("/failure.jsp")
                    .permitAll()
                    .and()
                    .logout().logoutUrl("/logout").logoutSuccessUrl("/login2.jsp")
                    .invalidateHttpSession(true)
                    .permitAll()
                    .and()
                    .csrf().disable();
        }
    
    }
    

    三、分布式 SpringSecurity

    可以参考此博客:

    简单授权:https://www.cnblogs.com/ifme/p/12184433.html
    
    JWT部分:https://www.cnblogs.com/ifme/p/12184587.html
    
    动态授权:https://www.cnblogs.com/fernfei/p/12194847.html
    
    

    三个工具类

    JsonUtils

    package com.example.utils;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author: 黑马程序员
     **/
    public class JsonUtils {
    
        public static final ObjectMapper mapper = new ObjectMapper();
    
        private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
    
        //json化
        public static String toString(Object obj) {
            if (obj == null) {
                return null;
            }
            if (obj.getClass() == String.class) {
                return (String) obj;
            }
            try {
                return mapper.writeValueAsString(obj);
            } catch (JsonProcessingException e) {
                logger.error("json序列化出错:" + obj, e);
                return null;
            }
        }
    
        //json解析
        public static <T> T toBean(String json, Class<T> tClass) {
            try {
                return mapper.readValue(json, tClass);
            } catch (IOException e) {
                logger.error("json解析出错:" + json, e);
                return null;
            }
        }
    
        //解析list的json数据
        public static <E> List<E> toList(String json, Class<E> eClass) {
            try {
                return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
            } catch (IOException e) {
                logger.error("json解析出错:" + json, e);
                return null;
            }
        }
    
        //json转map
        public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
            try {
                return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
            } catch (IOException e) {
                logger.error("json解析出错:" + json, e);
                return null;
            }
        }
    
        //json解析自定义类型
        public static <T> T nativeRead(String json, TypeReference<T> type) {
            try {
                return mapper.readValue(json, type);
            } catch (IOException e) {
                logger.error("json解析出错:" + json, e);
                return null;
            }
        }
    }
    
    
    

    JwtUtils

    package com.example.utils;
    
    import com.example.domain.Payload;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.joda.time.DateTime;
    
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.util.Base64;
    import java.util.UUID;
    
    /**
     * @author: 黑马程序员
     * 生成token以及校验token相关方法
     */
    public class JwtUtils {
    
        private static final String JWT_PAYLOAD_USER_KEY = "user";
    
        /**
         * 私钥加密token
         *
         * @param userInfo   载荷中的数据
         * @param privateKey 私钥
         * @param expire     过期时间,单位分钟
         * @return JWT
         */
        public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
            return Jwts.builder()
                    .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                    .setId(createJTI())
                    .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                    .signWith(privateKey, SignatureAlgorithm.RS256)
                    .compact();
        }
    
        /**
         * 私钥加密token
         *
         * @param userInfo   载荷中的数据
         * @param privateKey 私钥
         * @param expire     过期时间,单位秒
         * @return JWT
         */
        public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
            return Jwts.builder()
                    .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                    .setId(createJTI())
                    .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                    .signWith(privateKey, SignatureAlgorithm.RS256)
                    .compact();
        }
    
        /**
         * 公钥解析token
         *
         * @param token     用户请求中的token
         * @param publicKey 公钥
         * @return Jws<Claims>
         */
        private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
            return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        }
    
        private static String createJTI() {
            return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
        }
    
        /**
         * 获取token中的用户信息
         *
         * @param token     用户请求中的令牌
         * @param publicKey 公钥
         * @return 用户信息
         */
        public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
            Jws<Claims> claimsJws = parserToken(token, publicKey);
            Claims body = claimsJws.getBody();
            Payload<T> claims = new Payload<>();
            claims.setId(body.getId());
            claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
            claims.setExpiration(body.getExpiration());
            return claims;
        }
    
        /**
         * 获取token中的载荷信息
         *
         * @param token     用户请求中的令牌
         * @param publicKey 公钥
         * @return 用户信息
         */
        public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
            Jws<Claims> claimsJws = parserToken(token, publicKey);
            Claims body = claimsJws.getBody();
            Payload<T> claims = new Payload<>();
            claims.setId(body.getId());
            claims.setExpiration(body.getExpiration());
            return claims;
        }
    }
    
    

    RsaUtils

    package com.example.utils;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.security.*;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    
    /**
     * @author 黑马程序员
     */
    public class RsaUtils {
    
        private static final int DEFAULT_KEY_SIZE = 2048;
    
        /**
         * 从文件中读取公钥
         *
         * @param filename 公钥保存路径,相对于classpath
         * @return 公钥对象
         * @throws Exception
         */
        public static PublicKey getPublicKey(String filename) throws Exception {
            byte[] bytes = readFile(filename);
            return getPublicKey(bytes);
        }
    
        /**
         * 从文件中读取密钥
         *
         * @param filename 私钥保存路径,相对于classpath
         * @return 私钥对象
         * @throws Exception
         */
        public static PrivateKey getPrivateKey(String filename) throws Exception {
            byte[] bytes = readFile(filename);
            return getPrivateKey(bytes);
        }
    
        /**
         * 获取公钥
         *
         * @param bytes 公钥的字节形式
         * @return
         * @throws Exception
         */
        private static PublicKey getPublicKey(byte[] bytes) throws Exception {
            bytes = Base64.getDecoder().decode(bytes);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePublic(spec);
        }
    
        /**
         * 获取密钥
         *
         * @param bytes 私钥的字节形式
         * @return
         * @throws Exception
         */
        private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
            bytes = Base64.getDecoder().decode(bytes);
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePrivate(spec);
        }
    
        /**
         * 根据密文,生存rsa公钥和私钥,并写入指定文件
         *
         * @param publicKeyFilename  公钥文件路径
         * @param privateKeyFilename 私钥文件路径
         * @param secret             生成密钥的密文
         */
        public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            SecureRandom secureRandom = new SecureRandom(secret.getBytes());
            keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
            KeyPair keyPair = keyPairGenerator.genKeyPair();
            // 获取公钥并写出
            byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
            publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
            writeFile(publicKeyFilename, publicKeyBytes);
            // 获取私钥并写出
            byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
            privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
            writeFile(privateKeyFilename, privateKeyBytes);
        }
    
        private static byte[] readFile(String fileName) throws Exception {
            return Files.readAllBytes(new File(fileName).toPath());
        }
    
        private static void writeFile(String destPath, byte[] bytes) throws IOException {
            File dest = new File(destPath);
            if (!dest.exists()) {
                dest.createNewFile();
            }
            Files.write(dest.toPath(), bytes);
        }
    }
    
    

    pojo类Payload

    package com.example.domain;
    
    import lombok.Data;
    
    import java.util.Date;
    
    /**
     * @author john
     * @date 2020/1/12 - 9:15
     */
    @Data
    public class Payload<T> {
        private String id;
        private T userInfo;
        private Date expiration;
    }
    
    

    依赖

            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-api</artifactId>
                <version>0.10.7</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-impl</artifactId>
                <version>0.10.7</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-jackson</artifactId>
                <version>0.10.7</version>
            </dependency>
    
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.9.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </dependency>
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
            </dependency>
    

    JWT重写登录逻辑

    
    public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
    
        private AuthenticationManager authenticationManager;
        private RsaKeyProperties properties;
    
        public JwtLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties properties) {
            this.authenticationManager = authenticationManager;
            this.properties = properties;
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    
            SysUser sysUser=null;
            try {
                sysUser = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);
                String username = sysUser.getUsername();
                String password = sysUser.getPassword();
    
                if (username == null) {
                    username = "";
                }
                if (password == null) {
                    password = "";
                }
                username = username.trim();
    
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                        username, password);
    
    
                return authenticationManager.authenticate(authRequest);
            } catch (Exception e) {
    
                try {
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    PrintWriter writer = response.getWriter();
                    HashMap resultMap = new HashMap();
                    resultMap.put("code",401);
                    resultMap.put("msg","失败");
                    writer.write(new ObjectMapper().writeValueAsString(resultMap));
                    writer.flush();
                    writer.close();
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                }
                //e.printStackTrace();
                // throw new UsernameNotFoundException("验证失败");
                return null;
    
            }
    
    
        }
    
    
        @Override
        public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
            SysUser user=new SysUser();
            user.setUsername(authResult.getName());
            user.setRoles((List<SysRole>) authResult.getAuthorities());
            // 获取对象并强转
            //SysUser s2 = (SysUser) authResult.getPrincipal();
            String token = JwtUtils.generateTokenExpireInMinutes(user, properties.getPrivateKey(), 24 * 60);
            response.addHeader("Authorization","Bearer "+token);
    
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_OK);
                PrintWriter writer = response.getWriter();
                HashMap resultMap = new HashMap();
                resultMap.put("code",HttpServletResponse.SC_OK);
                resultMap.put("msg","认证通过");
                writer.write(new ObjectMapper().writeValueAsString(resultMap));
                writer.flush();
                writer.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }
    

    JWT重写验证逻辑

    
    public class JwiVerifyFilter extends BasicAuthenticationFilter {
    
        private RsaKeyProperties prop;
    
        public JwiVerifyFilter(AuthenticationManager authenticationManager,RsaKeyProperties prop) {
            super(authenticationManager);
            this.prop=prop;
        }
    
        @Override
        public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
            final boolean debug = this.logger.isDebugEnabled();
    
            String header = request.getHeader("Authorization");
    
            if (header == null || !header.startsWith("Bearer ")) {
                chain.doFilter(request, response);
    
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_OK);
                PrintWriter writer = response.getWriter();
                HashMap resultMap = new HashMap();
                resultMap.put("code",HttpServletResponse.SC_OK);
                resultMap.put("msg","请登录");
                writer.write(new ObjectMapper().writeValueAsString(resultMap));
                writer.flush();
                writer.close();
                return;
            }else {
                String token = header.replace("Bearer ", "");
                Payload<SysUser> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), SysUser.class);
                SysUser user = payload.getUserInfo();
                if(user!=null){
                    UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getRoles());
                    SecurityContextHolder.getContext().setAuthentication(authResult);
                }
                chain.doFilter(request, response);
            }
    
        }
    }
    

    配置类里添加 Filter

            http.addFilter(new JwtLoginFilter(super.authenticationManager(),prop))
                    .addFilter(new JwiVerifyFilter(super.authenticationManager(),prop))
    

    四、动态授权

    动态授权:https://www.cnblogs.com/fernfei/p/12194847.html

    此人博客比较完整

    这里记录几个主要的地方的代码

    Security配置类

        @Autowired
        CustomAccessDecisionManger customAccessDecisionManger;
    
        @Autowired
        CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                            object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource );
                            object.setAccessDecisionManager(customAccessDecisionManger);
                            return object;
                        }
                    })
        }
    

    负责解析 url 对应需要的角色

    
    @Component
    public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        AntPathMatcher antPathMatcher=new AntPathMatcher();
    
        @Autowired
        MenuService menuService;
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    
            String requestUrl = ((FilterInvocation) object).getRequestUrl();
    
            List<Menu> menus = menuService.getAllMenuWithRoles();
    
            for (Menu menu:menus){
                if (antPathMatcher.match(requestUrl,menu.getUrl())){
                    List<Role> roles=menu.getRoles();
                    String[] roleNames=new String[roles.size()];
                    for (int i = 0; i < roles.size(); i++) {
                        roleNames[i]=roles.get(i).getName();
                    }
    
                    return SecurityConfig.createList(roleNames);
                }
            }
            // 给一个缺省角色,表示 url 没有需要的角色
            return SecurityConfig.createList("ROLE_DEF");
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
    
    

    判断登录的用户是否拥有对应角色

    @Component
    public class CustomAccessDecisionManger implements AccessDecisionManager {
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            for (ConfigAttribute configAttribute :configAttributes){
    
                if ("ROLE_DEF".equals(configAttribute.getAttribute())){
                    // 到了这里说明是不需要权限的 url
                    if (authentication instanceof AnonymousAuthenticationToken){
                        // 即使不需要权限 也禁止匿名用户访问
                        // throw new AccessDeniedException("匿名用户 不可以访问");
                        return;
                    }else {
                        // 直接放行
                        return;
                    }
                }
                // authentication 存放了登录的用户的所有信息
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                for (GrantedAuthority authority: authorities){
                    if (authority.getAuthority().equals(configAttribute.getAttribute())){
                        return;
                    }
                }
    
            }
            throw new AccessDeniedException("权限不足");
        }
    
        @Override
        public boolean supports(ConfigAttribute attribute) {
            return true;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
    

    相关文章

      网友评论

          本文标题:SpringSecurity

          本文链接:https://www.haomeiwen.com/subject/tuirkktx.html