springsecuitry 学习笔记

where there is desire,there is gonna be aflame,and where there is a flame,someone's bound to get burned,But just because it burns doesn't mean you're gonna die,You've gotta get up and try,try,try

Spring Security

Spring Security

提供一整套完整的Web 应用安全性的完整解决方案

用户认证 Authentication

  • 验证用户是否为系统中的合法主体

用户授权 Authorization

  • 验证某个用户是否有权限执行某个操作

模块

  1. Core - spring-security-core.jar (核心模块)

  2. Remoting - spring - security -remoting .jar

  3. Web - spring -security -web .jar

  4. Config - spring -security -config.jar

  5. LDAP - spring - security - ldap.jar

  6. OAuth 2.0 Core - spring.-security- oauth2 -core.jar

  7. OAuth 2.0 Client - spring - security - oauth2 -client.jar

  8. OAuth 2.0 JSOE - spring - security - oauth2 -jose.jar

  9. ACL - spring -security-acl.jar

  10. CAS - spring -security - cas.jar

  11. OpenID -spring - security-openid.jar

  12. Test - spring-security-test.jar

spring boot 引入依赖后 提供了自动化的配置方案 ,可以使用更少的配置来使用 spring security

<!--        引入的依赖 security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

默认用户名

user

默认 密码 启动时会随机生成

springSecurity 底层是一个过滤器链

FilterSecurityInterceptor: 方法级过滤器,位于过滤器最底层

ExceptionTranslationFilter: 异常过滤器 ,用来处理在认证授权过程中抛出的异常

UsernamePasswordAuthenticationFilter: 对/login的POST请求做拦截,效验表单中的用户名

UserDerailsService 接口 实现自定义逻辑

实际开发中,用户账号密码,都是数据库中查询出来,需要自定义认证逻辑

PasswordEncoder 接口 加密

Web 权限方案

设置登录的用户名和密码

加载方式 配置文件,配置 没有设置用户名密码,再去 UserDetailService

方式一: 通过配置文件

application.properties

spring.security.user.name=
spring.security.user.password= 

方式二: 通过配置类实现

继承 WebSecurityConfigurerAdapter实现配置类

  • 重写 void configure(AuthenticationManagerBuilder auth)

public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * inMemoryAuthentication() 在内存中
         *witUser(用户名)   password(密码)  roles( 角色)
         * 密码一般做加密处理 BcryptPassworedEncoder
         */
        BCryptPasswordEncoder PasswordEncoder = new BCryptPasswordEncoder();
        String password = PasswordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("Lf").password("123").roles("admin");
​
    }
}

注意: 需要 PasswordEncoder 对象 ——在配置类添加

PasswordEncoder 是一个接口 他的实现类 BCryptPasswordEncoder

@Bean
PasswordEncoder password(){ return new BCryptPasswordEncoder(); }

方式三: 自定义编写实现类 操作 主要使用

1.编写配置类

/**
 * 方式三: 自定义实现 用户名 密码
 */
@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
​
    //UserDetailsService 接口,  自动注入他的实现类  需要自己编写
    @Autowired
    private UserDetailsService userDetailsService;
​
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         *serDetailsService() 使用自定的实现类
         */
        auth.userDetailsService(userDetailsService).passwordEncoder( password() );
    }
​
    @Bean
    PasswordEncoder password(){ return new BCryptPasswordEncoder(); }
}

2.编写实现类

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
​
    /**
     * UserDetails  构建用 户名 密码
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        /**
         *  User 是 security 包下的
         *  参数三  authorities: 参数类型集合   切不能为 null
         */
        //手动设置了一个角色     TODO 通常开发中从数据库中获取
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
​
        return new User("Lf",new BCryptPasswordEncoder().encode("123")
                ,auths);
    }
}

通过数据进行查询

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
​
    @Autowired
    private UserMapper userMapper;
​
    /**
     * UserDetails  构建用 户名 密码
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
​
        //手动设置了一个角色     TODO 通常开发中从数据库中获取
        List<GrantedAuthority> auths = AuthorityUtils.
                commaSeparatedStringToAuthorityList("role");
​
        //根据用户名来查询数据库
        QueryWrapper<Users> wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users users = userMapper.selectOne(wrapper);
        //TODO 做非空判断   空就是认证失败
​
​
        /**
         *  User 是 security 包下的
         *  参数三  authorities: 参数类型集合   切不能为 null
         */
        return new User("Lf",new BCryptPasswordEncoder().encode("123")
                ,auths);
    }
}

自定义登录页面 和 跳过认证访问

@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
    ......
        @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.formLogin()   //自定义 登录页面
               .loginPage("/login.html") //登录页面
               .loginProcessingUrl("/user/login")   //访问的 路径
               .defaultSuccessUrl("/test/index").permitAll()     //登录成功后跳转的路径
​
               .and().authorizeRequests()  //设置的路径不需要拦截, 可以直接访问
                    .antMatchers("/","test/hello","/user/login").permitAll()
​
               .anyRequest().authenticated()  //任何请求都可以访问
​
               .and().csrf().disable(); //关闭csrf认证方式  防护
    }
}

登录页面

用户名 密码 必须是 username 和 password 否则springSecurity 得不到提交过来的数据

在调用 UsernamePasswordAuthenticationFilter 拦截器中

    private String usernameParameter = "username";
    private String passwordParameter = "password";

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {}

        
    @Nullable
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(this.usernameParameter);
    }        

通过post 提交 来读取表单中的值

角色或权限 进行访问控制

hasAuthority() 单个权限认证

boolean hasAuthority()

需要有指定的权限 ,有返回true ,无返回 false

此方法只能认证有一个角色,不能认证多个如 “admin" "user"

@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
    ....;
     @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.formLogin()   //自定义 登录页面
  				......;
               .and().authorizeRequests()
                    //TODO 此页面登录用户 需要有admins  权限才可以访问
                    .antMatchers("/test/index").hasAuthority("admins")
				....;
    } 
}

测试 UserDetailsService 实现类中 User对象添加 :admins 角色

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    .....;
            return new User("Lf",new BCryptPasswordEncoder().encode("123")
                ,"admins");
}

hasAnyAuthority() 任意个权限

主体中有任何提供的角色(给定的作为一个逗号分隔的字符串列表) 则返回true

@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
    ....;
     @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.formLogin()   //自定义 登录页面
  				......;
               .and().authorizeRequests()
               	//TODO 此页面需要多个权限中的任何一个 就可以访问
               	.antMatchers("/text/index").hasAnyAuthority("admins,manager")
				....;
    } 
}

hasRole()

如权限role

hasRole的原码中 会更改为 ROLE_role 加ROLE_ 前缀

private static String hasRole(String role) {
        Assert.notNull(role, "role cannot be null");
        if (role.startsWith("ROLE_")) {
            throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
        } else {
            return "hasRole('ROLE_" + role + "')";  //增加前缀
        }
    }

编写是主要前缀

hasAnyRole()

多个角色中满足一个角色即可

@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
    ....;
     @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.formLogin()   //自定义 登录页面
  				......;
                    //TODO 当前登录用户需要有 role 这个权限才能登录   原码会改参数为 ROLB_role
                    .antMatchers("/text/index").hasRole("role")

                    //TODO 当前登录用户
                    .antMatchers("/text/index").hasAnyRole("admin,role,ROLE_admin,ROLE_role")
				....;
    } 
}

自定义没有权限的 访问页面

配置类中

@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
	.....;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置没有权限访问页面
        http.exceptionHandling().accessDeniedPage("/unauth");
        ....
    }
}

常用注解

@Secured 用户具有某个角色,可以访问方法

在启动类,或配置类上开启注解

@EnableGlobalMethodSecurity(securedEnabled = true) //开启secured注解
@GetMapping("update")
@Secured({"ROLE_sale","ROLE_admi","admin"})
public String update(){
    return "hello update";
}

用户具有相对于的角色才能访问

@PreAuthorize

进入方法前做权限的验证

@EnableGlobalMethodSecurity(prePostEnabled = true) //开启secured注解   配置器或启动类上添加
@GetMapping("PreAuthorize")
@PreAuthorize("hasAnyAuthority('admins')") //hasAnyAuthority 任意权限中的一个
public String PreAuthorize(){
    return "hello PreAuthorize";
}

@PostAuthorize

实际中用的比较少

在方法执行后进行权限验证 一般用于验证方法的返回值

@EnableGlobalMethodSecurity(proxyTargetClass = true)//配置类或者启动类上
@GetMapping("PostAuthorize")
@PostAuthorize("hasAnyAuthority('admins')")
public String PostAuthorize(){
    System.out.println("PostAuthorize....");  //用户没有权限,也会执行到 return 之前你的语句
    return "hello PostAuthorize";
}

@PsotFilter

权限验证之后,对数据进行过滤,留下用户名是 admin1 的数据

表达式中的 filterObject 引用的是方法返回值List 中的某一个元素

@PreAuthorize("hasRole('ROLE_管理员')")
@GetMapping("getAll")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List getAllUser(){
    ArrayList<Object> list = new ArrayList<>();
    list.add(new Users(1,"admin1","1111"));
    list.add(new Users(2,"admin2","3333"));//不会返回
    return list;
}

@PreFilter

对传入方法的数据进行过滤

@RequestMapping("getTstPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<Users> getTestPreFilter(@RequestBody List<Users> list){
    list.forEach(t ->{
        System.out.println( t.getId() + "\t" t.getUsername());
    });
    return list;
}

用户注销 操作

@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {
	.....;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //用户注销
     http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

spirngSecurity 自动登录

建立sql 表

create table persistent_logins(
	username varchar(64) NOT NULL,
	series varchar(64) NOT NULL,
	token varchar(64) NOT NULL,
	last_user timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (series)
)ENGINE = InnoDb DEFAULT CHARSET = utf8;

配置类 注入数据源 ,配置操作数据库对象

	@Configurable
public class SecurityConfigDerails extends WebSecurityConfigurerAdapter {

    //UserDetailsService 接口,  自动注入他的实现类  需要自己编写
    @Autowired
    private UserDetailsService userDetailsService;

        //注入操作对象  里面对应数据库
    @Bean
    public PersistentTokenRepository  persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        
//        jdbcTokenRepository.setCreateTableOnStartup(true); //在启动的是吧表创建
        return jdbcTokenRepository;
    }
    
 	 @Override
    protected void configure(HttpSecurity http) throws Exception {

       http.formLogin()   //自定义 登录页面
           .....             
           .and().rememberMe().tokenRepository( persistentTokenRepository())//上面的数据源
               .tokenValiditySeconds(60) //设置有效 60秒
               .userDetailsService(userDetailsService) //自己编写的service
			.....;
    } 
    
}

页面添加记住我 复选框 框架必须

name = "remember-me" 必须是这个名字

记住我: <input type = "checkbox" name = "remember-me" title = "记住密码"/><br/> 

CSRF 跨站请求伪造

spring Security 4.0开始,默认情况下回启用CSRF保户,以防止CSRF攻击

Spring Security CSRF 会针对 PATCH POST PUT 和 DELETE 方法进行防护

登录页面

<input type="hidden" th:name="{$_csrf.parameterName}" th:value="{_csrf.token}"/>

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div align = "center">
        <form method = "post" action ="update_token">
<!--            -->
            <input type="hidden" th:name="{$_csrf.parameterName}" th:value="{_csrf.token}"/>
            用户名:<input type="text" name="username"/><br/>
            密&nbsp;&nbsp;码;<input type="password" name="password"/><br/>

            <button type="submit">修改</button>
        </form>
    </div>
</body>
</html>

底层使用了 CsrfFilter 类 过滤器来实现的

Spring Security 微服务权限方案

单点登录、授权、JWT

微服务权限管理案列主要功能:

  1. 登录 认证

  2. 添加角色

  3. 为角色分配菜单

权限管理数据模型

编写常用工具类

实现PasswordEncoder 对密码进行加密,效验

作为工具类使用

@Component
public class DefaultPasswordEncoder implements PasswordEncoder {

    public DefaultPasswordEncoder(){
        this(-1);
    }

    public DefaultPasswordEncoder(int strength){   }

    /**
     * 加密
     * 进行MD5的加密
     */
    @Override
    public String encode(CharSequence charSequence) {
        return MD5.encrypt(charSequence.toString());
    }

    /**
     * 比对
     * 把密码进行比对
     * charSequence 加密之后的密码   String  传入的密码
     */
    @Override
    public boolean matches(CharSequence charSequence, String encoderPassword) {
        return encoderPassword.equals(MD5.encrypt(charSequence.toString()));
    }
}

token操作工具类

使用Jwt 生成token

public class TokenManager {

    //token 有效时长
    private long tokenEcpiration = 24*60*60*1000;

    //编码密钥  TODO 实际开发中需要 工具来生成对应字符串
    private String tokenSignKey = "123456";

    //1 根据用户名生成 token   使用JWT  根据用户名 生成token
    public String createToken(String username){
        String token = Jwts.builder().setSubject(username)  //setSubject 设置主体部分
                .setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration))   //Expiration设置以有效时间   通过系统时间+毫秒数
                .signWith(SignatureAlgorithm.HS512,tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return  token;
    }

    //2 根据token 字符串得到用户信息
    public String getUserInfoFromToken(String token){
        String userInfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token)
                .getBody().getSubject();
        return userInfo;
    }

    //3 删除token
    public void removeToken(String token){

    }
}

退出处理器 实现 LogoutHandler

删除token (redis 中的token)

cookie 设置过期时间

public class TokenLogoutHandler implements LogoutHandler {

    //自己编写的工具类
    private TokenManager tokenManager;

    private RedisTemplate redisTemplate;

    //通过有参构造器  来获取 工具类实体 和  redis 操作
    public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate){
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void logout(HttpServletRequest httpServletRequest,
                       HttpServletResponse httpServletResponse,
                       Authentication authentication) {
        //1.从header 里面获取 token


        //2.token 不为空,移除token  从redis删除 token
        String token = httpServletRequest.getHeader("token");
        if(token != null || token != ""){
            //移除
            tokenManager.removeToken(token);

            //从token 中获取用户名
            String userInfoFromToken = tokenManager.getUserInfoFromToken(token);

            //从redis 中删除
            redisTemplate.delete(userInfoFromToken);
        }
        ResponseUtil.out(httpServletResponse, R.ok());
    }
}

未授权统一的处理类

public class UnauthEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(httpServletResponse, R.error());
    }
}

过滤器 授权 认证

自定义认证过滤器

继承 UsernamePasswordAuthenticationFilter 类

重写方法

  • attemptAuthentication()    //获取表单提交的用户名和密码
  • successfulAuthentication() //认证成功执行的方法
  • unsuccessfulAuthentication()  //认证失败执行的方法
package com.lf.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lf.security.entit.SecurityUser;
import com.lf.security.entit.User;
import com.lf.security.security.TokenManager;
import com.lf.utils.utils.R;
import com.lf.utils.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

/**
 * 自定义过滤器  认证
 * @create 2021-12-13 10:32
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    //自定义的token 操作类
    private TokenManager tokenManager;

    private RedisTemplate redisTemplate;
    // security 提供的工具
    private AuthenticationManager authenticationManager;

    //有参构造来获取需要的资源
    public TokenLoginFilter(AuthenticationManager authenticationManager,
                            TokenManager tokenManager,
                            RedisTemplate redisTemplate){
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);  //是不是只能post 提交
        this.setRequiresAuthenticationRequestMatcher( //设置登录路径   提交方式
                new AntPathRequestMatcher("/admin/acl/login","POST"));
    }

    //1 获取表单提交域用户名和密码
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response)
            throws AuthenticationException {
        try {
            //获取表单数据 封装到 user对象中去
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                    user.getUsername(),user.getPassword(),new ArrayList<>()//参数 3 是权限
            ));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 2 认证成功调用的方法
     *  authResult  中有认证成功时 传递的数据  getPrincipal 获取用户认证信息
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult)
            throws IOException, ServletException {
        //认证成功之后先获取认证成功的用户信息   SecurityUser 自定义的实体类
        SecurityUser user = (SecurityUser)authResult.getPrincipal();

        //根据用户名生成token
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());

        //吧用户名和用户权限列表放到redis 中
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername()
        ,user.getPermissionValueList());

        //返回token    ResponseUtil自定义工具类
        ResponseUtil.out(response,R.ok().data("token",token));
    }

    //3 认证失败会调用的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response,
                                              AuthenticationException failed)
            throws IOException, ServletException {
            ResponseUtil.out(response,R.error());

    }
}

自定义过滤器 授权

继承BasicAuthenticationFilter 类

  • doFilterInternal()  //实现授权方法
package com.lf.security.filter;

import com.lf.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 自定义授权过滤器
 * @create 2021-12-13 11:38
 */
public class TokenAuthFilter extends BasicAuthenticationFilter {

    //自定义的 token工具
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenAuthFilter(AuthenticationManager authenticationManager){
        super(authenticationManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain)
            throws IOException, ServletException {
        //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);

        //判断如果有权限信息,放到权限的上下文中
        if(authRequest != null){
            SecurityContextHolder.getContext().setAuthentication(authRequest);
        }
        chain.doFilter(request,response);
    }


    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
        //从header 获取token
        String token = request.getHeader("token");
        if(token != null){
            String username = tokenManager.getUserInfoFromToken(token);

            //从redis 获取对应的权限列表
            List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username);

            //Collection<? extends GrantedAuthority>  需要的 权限类型
            Collection<GrantedAuthority> authority = new ArrayList<>();

            for(String permissionValue : permissionValueList){
                SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
                authority.add(auth);
            }


            //参数   用户名,token,权限列表信息
            return new UsernamePasswordAuthenticationToken(username,token,authority);
        }

        return null;
    }
}

配置类

@Configuration
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {

    private RedisTemplate redisTemplate;

    //自定义的工具类
    private TokenManager tokenManager;
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private UserDetailsService userDetailsService;


    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
                                  TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 配置设置
     * @param http
     * @throws Exception
     */
    //设置退出的地址和token,redis操作地址
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthEntryPoint())//没有权限访问  进入自定义的UnauthEntryPoint 处理类
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")//退出路径
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
                .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
                .addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }

    //調用 userDetailsService 和密碼處理  自定义的处理类
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    //不进行认证的路径,可以直接进行访问
    @Override
    public void configure(WebSecurity web)throws Exception{
        //ignoring 忽略     antMatchers具体路径  api 开头的路径不进行认证授权直接访问
        web.ignoring().antMatchers("/api/**");
    }
}

实现 userDetailsService

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据
        User user = userService.selectByUsername(username);

        //判断
        if(user == null){ throw new UsernameNotFoundException("用户不存在"); }

        // user 对象不是 Security 要求的返回对象, 需要转换为 实现了UserDetails 接口的对象
        com.lf.security.entit.User curUser = new com.lf.security.entit.User();

        BeanUtils.copyProperties(user,curUser);
        //根据用户查询用户权限列表
        List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
        SecurityUser securityUser = new SecurityUser();
        securityUser.setPermissionValueList(permissionValueList);

        return securityUser;
    }
}

原码 分析

UsernamePasswordAuthenticaitonFilter

对 /long的 Post 请求做拦截,效验表单中的用户名,密码

//调用父类过滤器  判断是否是post 提交
public abstract class AbstractAuthenticationProcessingFilter
    //执行
    pbulic void doFilter(){}
//调用子类方法进行身份认证,认证成功后,吧认证信息封装到对象里面
Authentication authResult;
try{
    authResult = this.attemptAuthentcation(request,response)
        if(authResult == null){
            return;
        }
}

子类UsernamePasswordAuthenticationFilter

//session 策略处理
this.sessionstrategy.onAuthentication(authResult , request,response);
//认证失败抛出异常,执行认证失败的方法
this.unsuccessfulAuthentication(request, response, var8);
//认证成功
this.successfulAuthentication(request, response, chain, authResult);

public class UsernamePasswordAuthenticationToken
    
    //未认证用户信息方法
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials)
    // 已认证用户信息方法
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean 

权限访问流程

ExceptionTranslationFilter

FilterSecuitryInterceptor

其他

跨域问题解决方案

配置类解决

  • gateway 网关

@Configuration
public class CorsConfig {

    //解决跨域
    @Bean
    public CorsWebFilter corsWebFilter(){
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedHeader("*");
        config.addAllowedOrigin("*");
        config.addAllowedMethod("*");


        org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource source =
                new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**",config);//所有路径都允许访问

        return new CorsWebFilter(source);
    }
}

底部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值