SpringSecurity

一、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;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352