Spring Security添加token授权登陆

需求:其他项目要跳转我们的springboot+springsecurity项目,需要授权特定token登陆。
实现思路:添加过滤器,拦截请求中的token,传递给Authentication鉴权,如果这个请求不带有授权信息,被拦截后,会跳转登录页面。

一.先来了解下springsecurity,熟悉的跳过该部分
1)Spring Security介绍:
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤器链,不同的功能经由不同的过滤器。要实现的功能就是在认证服务器上登录,然后获取Token,再访问资源服务器中的资源。核心功能:认证和授权。
在这里插入图片描述
2)Spring Security 认证流程:
在这里插入图片描述
3)Spring Security 项目搭建:

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

4)访问页面:
导入spring-boot-starter-security启动器后,Spring Security已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。
在浏览器输入:http://localhost:8080/ 进入Spring Security内置登录页面
用户名:user。
密码:项目启动,打印在控制台中。
5)自定义用户名和密码:
修改application.yml文件

# 静态用户,一般只在内部网络认证中使用,如:内部服务器1,访问服务器2  
spring:  
  security:  
    user:  
      name: test  # 通过配置文件,设置静态用户名  
      password: test # 配置文件,设置静态登录密码  

6)UserDetailsService详解:
什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过 「自定义逻辑控制认证逻辑」 。如果需要自定义逻辑时,只需要实现UserDetailsService接口。

@Component  
public class UserSecurity implements UserDetailsService {  
  
    @Autowired  
    private UserService userService;  
  
    @Override  
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {  
  
        User user = userService.login(userName);  
        System.out.println(user);  
        if (null==user){  
            throw new UsernameNotFoundException("用户名错误");  
        }  
        org.springframework.security.core.userdetails.User result =  
                new org.springframework.security.core.userdetails.User(  
                        userName,user.getPassword(), AuthorityUtils.createAuthorityList()  
                );  
        return result;  
    }  
  
}

7)PasswordEncoder密码解析器详解
「PasswordEncoder」 是SpringSecurity 的密码解析器,用户密码校验、加密 。自定义登录逻辑时要求必须给容器注入PaswordEncoder的bean对象,SpringSecurity 定义了很多实现接口 「PasswordEncoder」 满足我们密码加密、密码校验 使用需求PasswordEncoder密码解析器详解
自定义密码解析器:

/**   
* 凭证匹配器,用于做认证流程的凭证校验使用的类型   
* 其中有2个核心方法   
* 1. encode - 把明文密码,加密成密文密码   
* 2. matches - 校验明文和密文是否匹配   
* */  
public class MyMD5PasswordEncoder implements PasswordEncoder {  
  
    /**       
    * 加密       
    * @param charSequence  明文字符串      
     * @return       
     */  
    @Override  
    public String encode(CharSequence charSequence) {  
        try {  
            MessageDigest digest = MessageDigest.getInstance("MD5");  
            return toHexString(digest.digest(charSequence.toString().getBytes()));  
        } catch (NoSuchAlgorithmException e) {  
            e.printStackTrace();  
            return "";  
        }  
    }  
  
    /**       
    * 密码校验       
    * @param charSequence 明文,页面收集密码       
    * @param s 密文 ,数据库中存放密码      
    * @return       
     */  
    @Override  
    public boolean matches(CharSequence charSequence, String s) {  
        return s.equals(encode(charSequence));  
    }  
  
     /**       
     * @param tmp 转16进制字节数组       
     * @return 返回16进制字符串       
     */  
    private String toHexString(byte [] tmp){  
        StringBuilder builder = new StringBuilder();  
        for (byte b :tmp){  
            String s = Integer.toHexString(b & 0xFF);  
            if (s.length()==1){  
                builder.append("0");  
            }  
            builder.append(s);  
        }  
  
        return builder.toString();  
  
    }  
}  
/**    
* 加密    
* @return 加密对象    
* 如需使用自定义密码凭证匹配器 返回自定义加密对象    
* 例如: return new MD5PasswordEncoder();     
*/  
@Bean  
public PasswordEncoder passwordEncoder() {  
    return new BCryptPasswordEncoder(); //Spring Security 自带  
}  

8)登录配置
方式一 转发

http.formLogin()  
    .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username  
    .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password  
    .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login  
    .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login  
    .failureForwardUrl("/failure"); // 登录失败后,请求转发的位置。Security请求转发使用Post请求。默认转发到:loginPage?error  
    .successForwardUrl("/toMain"); // 用户登录成功后,请求转发到的位置。Security请求转发使用POST请求。  

方式二 :重定向

http.formLogin()  
    .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username  
    .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password  
    .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login  
    .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login  
 .defaultSuccessUrl("/toMain",true); //用户登录成功后,响应重定向到的位置。GET请求。必须配置绝对地址。  
  .failureUrl("/failure"); // 登录失败后,重定向的位置。  

方式三:自定义登录处理器

/*自定义登录失败处理器*/  
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {  
    private  String url;  
    private boolean isRedirect;  
  
  
    public MyAuthenticationFailureHandler(String url, boolean isRedirect) {  
        this.url = url;  
        this.isRedirect = isRedirect;  
    }  
  
    @Override  
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {  
        if (isRedirect){  
            httpServletResponse.sendRedirect(url);  
        }else {  
            httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);  
        }  
    }  
  
//get set 方法 省略 

自定义登录成功逻辑处理器

/**   
* 自定义登录成功后处理器   
* 转发重定向,有代码逻辑实现   
* */  
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {  
    private String url;  
    private boolean isRedirect;  
  
    public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {  
        this.url = url;  
        this.isRedirect = isRedirect;  
    }  
  
    /**       * @param request 请求对象 request.getRequestDispatcher.forward()       * @param response 响应对象 response.sendRedirect()       * @param authentication 用户认证成功后的对象。其中报换用户名权限结合,内容是       *                       自定义UserDetailsService       * */  
    @Override  
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {  
        if (isRedirect){  
            response.sendRedirect(url);  
        }else {  
            request.getRequestDispatcher(url).forward(request,response);  
        }  
    }  
  
//get set 方法 省略 

登录相关配置类

@Configuration  
@EnableWebSecurity  
public class SecurityConfig extends WebSecurityConfigurerAdapter {  
  
    @Autowired  
    private  UserSecurity userSecurity;  
    @Autowired  
    private PersistentTokenRepository persistentTokenRepository;  
  
  
    /**       
    * 加密       
    * @return 加密对象      
     * 如需使用自定义加密逻辑 返回自定义加密对象       
     * return new MD5PasswordEncoder(); return new SimplePasswordEncoder();       
     */  
    @Bean  
    public PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder(); //Spring Security 自带  
    }  
  
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        // 配置登录请求相关内容。  
        http.formLogin()  
            .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login  
            .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username  
            .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password  
            .loginProcessingUrl("/login") //设置登录 提交表单数据访问请求地址  
            .defaultSuccessUrl("/toMain")     
            .failureUrl("/toLogin");  
         //.successForwardUrl("/toMain")  
         //.failureForwardUrl("/toLogin");  
            //.successHandler(new LoginSuccessHandler("/toMain", true)) //自定义登录成功处理器  
                //.failureHandler(new LoginErrorHandler("/toLogin", true));  
  
        http.authorizeRequests()  
            //.antMatchers("/toLogin").anonymous() //只能匿名用户访问  
            .antMatchers("/toLogin", "/register", "/login", "/favicon.ico").permitAll() // /toLogin请求地址,可以随便访问。  
            .antMatchers("/**/*.js").permitAll() // 授予所有目录下的所有.js文件可访问权限  
            .regexMatchers(".*[.]css").permitAll() // 授予所有目录下的所有.css文件可访问权限  
            .anyRequest().authenticated(); // 任意的请求,都必须认证后才能访问。  
  
  
        // 配置退出登录  
        http.logout()  
                .invalidateHttpSession(true) // 回收HttpSession对象。退出之前调用HttpSession.invalidate() 默认 true  
                .clearAuthentication(true) // 退出之前,清空Security记录的用户登录标记。 默认 true  
                // .addLogoutHandler() // 增加退出处理器。  
                .logoutSuccessUrl("/") // 配置退出后,进入的请求地址。 默认是loginPage?logout  
                .logoutUrl("/logout"); // 配置退出登录的路径地址。和页面请求地址一致即可。  
  
        // 关闭CSRF安全协议。  
        // 关闭是为了保证完整流程的可用。  
        http.csrf().disable();  
    }  
  
  
   @Bean  
   public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){  
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();  
        jdbcTokenRepository.setDataSource(dataSource);  
        //jdbcTokenRepository.setCreateTableOnStartup(true);  
        return jdbcTokenRepository;  
    }  
}  

角色权限
「hasAuthority(String)」 判断角色是否具有特定权限

http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")  

「hasAnyAuthority(String …)」 如果用户具备给定权限中某一个,就允许访问

http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx")   

「hasRole(String)」 如果用户具备给定角色就允许访问。否则出现403

//请求地址为/admin/read的请求,必须登录用户拥有'管理员'角色才可访问  
http.authorizeRequests().antMatchers("/admin/read").hasRole("管理员")  

「hasAnyRole(String …)」 如果用户具备给定角色的任意一个,就允许被访问

//用户拥有角色是管理员 或 访客 可以访问 /guest/read  
http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理员", "访客")  

「hasIpAddress(String)」 请求是指定的IP就运行访问

//ip 是127.0.0.1 的请求 可以访问/ip  
http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")  

9)403 权限不足页面处理
编写类实现接口

/**   * @describe  403 权限不足   * @author: AnyWhere    */  
@Component  
public class MyAccessDeniedHandler implements AccessDeniedHandler {  
    @Override  
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {  
  
        response.setStatus(HttpServletResponse.SC_OK);  
  
        response.setContentType("text/html;charset=UTF-8");  
  
        response.getWriter().write(  
                "<html>" +  
                        "<body>" +  
                        "<div style='width:800px;text-align:center;margin:auto;font-size:24px'>" +  
                        "权限不足,请联系管理员" +  
                        "</div>" +  
                        "</body>" +  
                        "</html>"  
  
        );  
  
        response.getWriter().flush();//刷新缓冲区  
    }  
}  

配置类中配置exceptionHandling

// 配置403访问错误处理器。  
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/  

10)RememberMe(记住我)

@Configuration  
@EnableWebSecurity  
public class SecurityConfig extends WebSecurityConfigurerAdapter {  
  @Override  
  protected void configure(HttpSecurity http) throws Exception {  
    //配置记住密码  
    http.rememberMe()  
        .rememberMeParameter("remember-me") // 修改请求参数名。 默认是remember-me  
        .tokenValiditySeconds(14*24*60*60) // 设置记住我有效时间。单位是秒。默认是14天  
        .rememberMeCookieName("remember-me") // 修改remember me的cookie名称。默认是remember-me  
        .tokenRepository(persistentTokenRepository) // 配置用户登录标记的持久化工具对象。  
        .userDetailsService(userSecurity); // 配置自定义的UserDetailsService接口实现类对象  
  
  }  
  @Bean  
  public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){  
     JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();  
     jdbcTokenRepository.setDataSource(dataSource);  
     //jdbcTokenRepository.setCreateTableOnStartup(true);  
     return jdbcTokenRepository;  
  }  
}  

二.来了解下springsecurity的用户名密码登录实现,熟悉的跳过该部分
1.配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${http.login.fail.url}")
    private String loginFailUrl;

    @Value("${http.logout.success.url}")
    private String logoutSuccessUrl;

    @Value("${http.white.url.active}")
    private String whiteActiveUrl;

    @Autowired
    private Environment env;
   
    @Autowired
    public void configGlobal(AuthenticationManagerBuilder auth) throws Exception {        		auth.authenticationProvider(customAuthenticationProvider()).eraseCredentials(true);
    }
    
    //用户名和密码登陆处理
    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider();
    }
    
    /**
     * 添加用户名和密码登陆验证的过滤器
     */
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
    	CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        String[] whiteActiveUrls = whiteActiveUrl.split(",");
        List<String> whiteUrls = new ArrayList<String>();

        for (int i = 0; i < whiteActiveUrls.length; i++) {
            String item = whiteActiveUrls[i];
            item = env.getProperty("http.white.urls." + item);
            if (StringUtils.isEmpty(item)) {
                continue;
            }
            whiteUrls.add(item);
        }
        whiteUrls.add("/static/**");

        // 白名单
        http.authorizeRequests().antMatchers(whiteUrls.toArray(new String[whiteUrls.size()])).permitAll().requestMatchers(CorsUtils::isPreFlightRequest);
        http.headers().frameOptions().disable();

        // 定义登陆成功之后的页面跳转
  		http.formLogin().loginPage("/login").defaultSuccessUrl("/index").successForwardUrl("/index");
        
        //添加用户名密码过滤器。
        http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        
        // 登出相关url
        http.logout().logoutSuccessUrl(logoutSuccessUrl);
		// 要求对request都进行是否授权的验证
        http.authorizeRequests().anyRequest().authenticated();
    }
}

2.定义一个AuthenticationToken,他的实例包含了用户登陆时的信息,封装后用于校验。

public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {

    private static final long serialVersionUID = -1076492615339314113L;

    public CustomAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public CustomAuthenticationToken(Object principal, Object credentials,
            Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

3.过滤器的定义

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public CustomAuthenticationFilter() {
        //父类中定义了拦截的请求URL,/login的post请求,直接使用这个配置,也可以自己重写
        super(); 
        //添加了自定义的登陆失败处理器,配置文件中直接配置failureUrl没能直接生效
        super.setAuthenticationFailureHandler(new LoginFailureHandler());
    }

    /**
     * 这里主要是把 request中的用户名和密码参数取出来,封装CustomAuthenticationToken,然后getAuthenticationManager().authenticate(authRequest)进行校验
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    	throws AuthenticationException {
    	
    	if (!request.getMethod().equals(HttpMethod.POST.name())) {
    		throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

    	String username = obtainUsername(request).trim();
    	String password = obtainPassword(request).trim();
    	final int userNameLen = 32;
        if (username.length() == 0 || username.length() > userNameLen || password.length() == 0
                || password.length() > userNameLen) {
            throw new BadCredentialsException("username or password is wrong!");
        }

        CustomAuthenticationToken authRequest = new CustomAuthenticationToken(username, password);
        setDetails(request, authRequest);
        Authentication authentication = getAuthenticationManager().authenticate(authRequest);
        return authentication;
    }

    @Override
    protected String obtainUsername(HttpServletRequest request) {
        String username = super.obtainUsername(request);
        return username == null ? "" : username;
    }

    @Override
    protected String obtainPassword(HttpServletRequest request) {
        String password = super.obtainPassword(request);
        return password == null ? "" : password;
    }
}

4.具体的校验逻辑定义

public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserServiceFeignClient userServiceFeignClient;

    // 根用户拥有全部的权限
    private final List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority("CAN_SEARCH"), new SimpleGrantedAuthority("CAN_EXPORT"), new SimpleGrantedAuthority("CAN_IMPORT"), new SimpleGrantedAuthority("CAN_BORROW"), new SimpleGrantedAuthority("CAN_RETURN"), new SimpleGrantedAuthority("CAN_REPAIR"), new SimpleGrantedAuthority("CAN_DISCARD"), new SimpleGrantedAuthority("CAN_EMPOWERMENT"), new SimpleGrantedAuthority("CAN_BREED"));

    //这里通过用户名和密码的检查匹配判断登陆是否成功。
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    	if (authentication.isAuthenticated()) {
    		return authentication;
    	}
    	String username = authentication.getName();
        User user = null;
        try {
        	user = userServiceFeignClient.getUserByUsername(username);
        } catch (BusinessException e) {
        	 throw new BadCredentialsException("该用户不存在,用户名: " + username);
        }
        if (user == null) {
            throw new BadCredentialsException("该用户不存在,用户名: " + username);
        }
        String password;
		try {
			password = encode((String)authentication.getCredentials());
		} catch (NoSuchAlgorithmException e) {
			throw new BadCredentialsException("密码错误");
		}
        if (!password.equals(user.getPassword())) {
            throw new BadCredentialsException("密码错误");
        }
        //这个是自定义用户登陆信息
        SecurityUser securityUser = new SecurityUser();
        securityUser.setUsername(user.getUsername());
        securityUser.setPassword(user.getPassword());
        securityUser.setUid(user.getUid());
        return new CustomAuthenticationToken(securityUser, authentication.getCredentials(), authorities);
    }

    public static String encode(String str) throws NoSuchAlgorithmException{
		MessageDigest instance = MessageDigest.getInstance("MD5");
		byte[] digest = instance.digest(str.getBytes());
		StringBuffer sb = new StringBuffer();
		for (byte b : digest) {
			int j = b & 0xff;
			String hexString = Integer.toHexString(j);
			if (hexString.length() < 2) {
				hexString = "0" + hexString;
			}
			sb.append(hexString);
		}
		return sb.toString();
	}
    
    //这里定义provider是否被调用,需要执行结果为true才会执行验证逻辑
    @Override
    public boolean supports(Class<?> authentication) {
    	return CustomAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

这个就是用户名和密码登陆校验的逻辑。而token登陆的逻辑基本一致,只是校验token即可,不校验用户名密码。

三.token实现跳转实现参考代码:
1.用户名密码登录配置中添加token跳转配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${http.login.fail.url}")
    private String loginFailUrl;

    @Value("${http.logout.success.url}")
    private String logoutSuccessUrl;

    @Value("${http.white.url.active}")
    private String whiteActiveUrl;

    @Autowired
    private Environment env;
   
    
    /**
     * 此处给AuthenticationManager添加登陆验证的逻辑。
     * 这里添加了两个AuthenticationProvider分别用于用户名密码登陆的验证以及token授权登陆两种方式。
     * 在处理登陆信息的过滤器执行的时候会调用这两个provider进行登陆验证。
     */
    @Autowired
    public void configGlobal(AuthenticationManagerBuilder auth) throws Exception {        		auth.authenticationProvider(customAuthenticationProvider()).authenticationProvider(tokenAuthenticationProvider()).eraseCredentials(true);
    }
    
    //用户名和密码登陆处理
    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider();
    }
    
    //token登陆处理
    @Bean
    public TokenAuthenticationProvider tokenAuthenticationProvider() {
    	return new TokenAuthenticationProvider();
    }

    /**
     * 添加token登陆验证的过滤器
     */
    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
    	TokenAuthenticationFilter filter = new TokenAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }
    
    /**
     * 添加用户名和密码登陆验证的过滤器
     */
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
    	CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        String[] whiteActiveUrls = whiteActiveUrl.split(",");
        List<String> whiteUrls = new ArrayList<String>();

        for (int i = 0; i < whiteActiveUrls.length; i++) {
            String item = whiteActiveUrls[i];
            item = env.getProperty("http.white.urls." + item);
            if (StringUtils.isEmpty(item)) {
                continue;
            }
            whiteUrls.add(item);
        }
        whiteUrls.add("/static/**");

        // 白名单
        http.authorizeRequests().antMatchers(whiteUrls.toArray(new String[whiteUrls.size()])).permitAll().requestMatchers(CorsUtils::isPreFlightRequest);
        http.headers().frameOptions().disable();

        // 定义登陆成功之后的页面跳转
  		http.formLogin().loginPage("/login").defaultSuccessUrl("/index").successForwardUrl("/index");
        
        //分别添加 token 过滤器和 用户名密码过滤器。
        http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        
        // 登出相关url
        http.logout().logoutSuccessUrl(logoutSuccessUrl);
		// 要求对request都进行是否授权的验证
        http.authorizeRequests().anyRequest().authenticated();
    }
}

配置文件定义了两个登陆验证的过滤器TokenAuthenticationFilter和UsernamePasswordAuthenticationFilter;
TokenAuthenticationFilter-验证token登录逻辑;
UsernamePasswordAuthenticationFilter-验证用户名密码登录逻辑;
TokenAuthenticationFilter过滤器会对我们指定的跳转url进行拦截,通过我们定义的验证逻辑判断登陆信息是否正确。如果正确将授权信息添加到服务器。

2.token授权过滤器定义:

public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private String tokenParameter = "token";
    
    /**
     * @param defaultFilterProcessesUrl
     */
    public TokenAuthenticationFilter() {
        super("/quicklogin");//放行的请求url
        super.setAuthenticationFailureHandler(new LoginFailureHandler());//失败页面
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    	throws AuthenticationException {
    	
    	if (!request.getMethod().equals(HttpMethod.POST.name())) {
    		throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

    	String token = obtainToken(request);
        if (token == null || token.length() == 0) {
        	throw new BadCredentialsException("uid or token is null.");
        }

        TokenAuthenticationToken authRequest = new TokenAuthenticationToken(token);

        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
  
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainToken(HttpServletRequest request) {
        String token = request.getParameter(this.tokenParameter);
        return token == null ? "" : token.trim();
    }
}

3.AuthenticationProvider的定义:

public class TokenAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserServiceFeignClient userServiceFeignClient;
	
    // 根用户拥有全部的权限
    private final List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority("CAN_SEARCH"), new SimpleGrantedAuthority("CAN_EXPORT"), new SimpleGrantedAuthority("CAN_IMPORT"), new SimpleGrantedAuthority("CAN_BORROW"), new SimpleGrantedAuthority("CAN_RETURN"), new SimpleGrantedAuthority("CAN_REPAIR"), new SimpleGrantedAuthority("CAN_DISCARD"), new SimpleGrantedAuthority("CAN_EMPOWERMENT"), new SimpleGrantedAuthority("CAN_BREED"));

    
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		
		if (authentication.isAuthenticated()) {
			return authentication;
		}
		//获取过滤器封装的token信息
		TokenAuthenticationToken authenticationToken = (TokenAuthenticationToken) authentication;
		User user = userServiceFeignClient.getUserTokenByToken((String)authenticationToken.getPrincipal());
//        不通过
		if (user == null) {
        	 throw new BadCredentialsException("授权token无效,请重新登陆");
        }
        SecurityUser securityUser = new SecurityUser();

        securityUser.setUsername(user.getUsername());
        securityUser.setPassword(user.getPassword());
        securityUser.setUid(user.getUid());
        
        TokenAuthenticationToken authenticationResult = new TokenAuthenticationToken(securityUser, authorities);

        return authenticationResult;
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return TokenAuthenticationToken.class.isAssignableFrom(authentication);
	}
}

4.AuthenticationToken的定义:

public class TokenAuthenticationToken extends UsernamePasswordAuthenticationToken {

    private static final long serialVersionUID = -6231962326068951783L;


    public TokenAuthenticationToken(Object principal) {
        super(principal, "");
    }


    public TokenAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(principal, "", authorities);
    }

}

定义AuthenticationToken,他的实例包含了用户登陆时的信息,封装后用于校验。
5.登陆失败信息的获取:
前文我们自定义了一个登陆失败的handler如下。只是定义了失败后跳转的URL。

@Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
	public LoginFailureHandler() {
		super("/login?error=true");
	}
}

前端获取登陆失败的信息

<p th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}" ></p>
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值