栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot中使用springsecurity

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot中使用springsecurity

1:概述 1.1:认证和授权的区别

认证就是证明你是谁,而授权是你能做什么,比如你要连接mysql数据库,此时需要用户名和密码,这个过程就是认证,而登陆之后你可以做什么操作,比如创建表,删除表,查询数据,等,这就是授权。其实这个过程对于所有的系统的都是通用的,你登录系统需要录入认证信息来登录,这就是认证,登录之后你能看到什么,干什么,这就是授权。

在系统中,认证对应的应该就是登录功能,而授权对应的就是权限控制功能。本文我们要分析的springsecurity就可以提供二者的功能。

2:实例程序 2.1:引入相关起步依赖

    
        
            org.springframework.boot
            spring-boot-dependencies
            2.1.14.RELEASE
            pom
            import
        
    



    
    
        org.springframework.boot
        spring-boot-starter-web
    

    
        org.springframework.boot
        spring-boot-starter-security
    

2.2:main
@SpringBootApplication
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }
}
2.3:配置文件

application.yml:

spring:
  # Spring Security 配置项,对应 org.springframework.boot.autoconfigure.security.SecurityProperties 配置类
  # 即这里配置的信息会通过该类进行接收和封装,后续获取该类的信息也是通过该类了
  security:
    # 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。
    user:
      name: user # 账号
      password: user # 密码SecurityProperties
      roles: ADMIN # 拥有角色

这里的配置会被封装到类org.springframework.boot.autoconfigure.security.SecurityProperties,源码如下:

// org.springframework.boot.autoconfigure.security.SecurityProperties
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
	public static final int BASIC_AUTH_ORDER = Ordered.LOWEST_PRECEDENCE - 5;

	public static final int IGNORED_ORDER = Ordered.HIGHEST_PRECEDENCE;

	public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
    // 内部静态类,过滤认证
	private final Filter filter = new Filter();
    // 内部静态类,封装用户名,密码,角色等信息
	private User user = new User();

	public User getUser() {
		return this.user;
	}

	public Filter getFilter() {
		return this.filter;
	}

	public static class Filter {
		private int order = DEFAULT_FILTER_ORDER;

		private Set dispatcherTypes = new HashSet<>(
				Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));

		public int getOrder() {
			return this.order;
		}

		public void setOrder(int order) {
			this.order = order;
		}

		public Set getDispatcherTypes() {
			return this.dispatcherTypes;
		}

		public void setDispatcherTypes(Set dispatcherTypes) {
			this.dispatcherTypes = dispatcherTypes;
		}

	}
    
    // 用户对象
	public static class User {
	    // 默认的用户名,这里是user
		private String name = "user";
		// 默认的密码,这里是通过UUID生成的随机字符串
		private String password = UUID.randomUUID().toString();
        
        // 授权给用户的角色列表
		private List roles = new ArrayList<>();
        
        // 标记密码是否为生成的
		private boolean passwordGenerated = true;

		public String getName() {
			return this.name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			if (!StringUtils.hasLength(password)) {
				return;
			}
            // 如果是使用了用户的密码则将密码是否为生成的标记置为false
			this.passwordGenerated = false;
			this.password = password;
		}

		public List getRoles() {
			return this.roles;
		}

		public void setRoles(List roles) {
			this.roles = new ArrayList<>(roles);
		}

		public boolean isPasswordGenerated() {
			return this.passwordGenerated;
		}

	}
}

其对应的自动配置类是org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,其在spring.factories中的配置如下:

该类源码如下:

// org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {

	private static final String NOOP_PASSWORD_PREFIX = "{noop}";

	private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\{.+}.*$");

	private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

	@Bean
	@ConditionalOnMissingBean(
			type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
	@Lazy
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider passwordEncoder) {
        // 从SecurityProperties中获取创建的用户对象
		SecurityProperties.User user = properties.getUser();
        // 从用户对象中获取角色
		List roles = user.getRoles();
        // 创建InMemoryUserDetailsManager的spring bean
		return new InMemoryUserDetailsManager(
				User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
						.roles(StringUtils.toStringArray(roles)).build());
	}
    
    // 获取或者是推演一个用户的密码
	private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
		String password = user.getPassword();
		if (user.isPasswordGenerated()) {
			logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
		}
		if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
			return password;
		}
		return NOOP_PASSWORD_PREFIX + password;
	}

}

如下是我本地debug运行时的结果:

从getOrDeducePassoword方法可以看到当没有在配置文件中指定密码使用生成的密码时会打印UUID生成的密码,如下是我本地的输出:

2.4:定义一个Controller
@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/demo")
    public String demo() {
        return "示例返回";
    }

}

启动项目访问http://localhost:8049/admin/demo,会被springsecurity拦截到登录界面,如下:

执行拦截工作的是类org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter,这是servlet过滤器javax.servlet.Filter的一个子类,如下是跳转到登录页面相关源码:

public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    			throws IOException, ServletException {
    		HttpServletRequest request = (HttpServletRequest) req;
    		HttpServletResponse response = (HttpServletResponse) res;
    
    		boolean loginError = isErrorPage(request);
    		boolean logoutSuccess = isLogoutSuccess(request);
    		if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
                // 生成登录的HTML
    			String loginPageHtml = generateLoginPageHtml(request, loginError,
    					logoutSuccess);
    			response.setContentType("text/html;charset=UTF-8");
    			response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
    			response.getWriter().write(loginPageHtml);
    
    			return;
    		}
    
    		chain.doFilter(request, response);
    	}
    
    	private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
    			boolean logoutSuccess) {
    		String errorMsg = "Invalid credentials";
    
    		if (loginError) {
    			HttpSession session = request.getSession(false);
    
    			if (session != null) {
    				AuthenticationException ex = (AuthenticationException) session
    						.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    				errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
    			}
    		}
    
    		StringBuilder sb = new StringBuilder();
    
    		sb.append("n"
    				+ "n"
    				+ "  n"
    				+ "    n"
    				+ "    n"
    				+ "    n"
    				+ "    n"
    				+ "    Please sign inn"
    				+ "    n"
    				+ "    n"
    				+ "  n"
    				+ "  n"
    				+ "     n");
    
    		String contextPath = request.getContextPath();
    		if (this.formLoginEnabled) {
    			sb.append("      

在登录界面录入配置的用户名密码就可以登录了,登录完成后会自动重定向到最初访问的页面,如下:

3:进阶使用

本部分我们尝试在前一部分基础上进行一些增强定制工作。

3.1:实例1 3.1.1:SecurityConfig

对springsecurity进行相关配置,通过实现类WebSecurityConfigurerAdapter进行定制,定义如下:

// 使其成为java config类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

如下方法是对用户和角色等信息进行设置:

// 配置认证和授权相关信息
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 使用内存中的InMemoryUserDetailsManager
    auth.inMemoryAuthentication()
            // 指定密码编码器为无擦操作密码编码器,即不使用密码编码器,但是如果不指定的话会报错,所以这里还是需要指定
            .passwordEncoder(NoOpPasswordEncoder.getInstance())
            // 配置admin账号以及其角色
            .withUser("admin").password("admin").roles("ADMIN")
            // 配置普通用户账号以及其角色
            .and().withUser("normal").password("normal").roles("NORMAL");
}

inMemoryAuthentication()是设置使用基于内存中的InMemoryUserDetailsManager类,另外还有基于JDBC的JdbcUserDetailsManager,二者都是顶层接口org.springframework.security.core.userdetails.UserDetailsService,如下图:

一般我们自定义接口UserDetailsService的子类来完成相关的设置,这样更加灵活的进行用户信息的设置和读取。

指定密码编码器为无擦操作密码编码器,即不使用密码编码器,但是如果不指定的话会报错,所以这里还是需要指定,生产上推荐使用BCryptPasswordEncoder,密码编码器对应的顶层接口是org.springframework.security.crypto.password.PasswordEncoder,源码如下:

// 用来编码密码的服务接口类,比较优秀的实现类是BCryptPasswordEncoder
public interface PasswordEncoder {

	// 对原始密码进行编码
	String encode(CharSequence rawPassword);

	// 验证传入的原始密码是否和存储的编码后的密码一致,一致则返回true,否则返回false
    // 注意:编码后的密码为了安全性永远不能被解码,即不能编码后的密码解码和原始密码比较是否一致
	boolean matches(CharSequence rawPassword, String encodedPassword);

    // 判断当前已经编码的密码是否不足够安全,是的话则返回true,否则返回false
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}
}

接下来对http访问相关方法重写,设置url访问等信息,如下:

// 配置访问控制相关信息
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 配置请求地址的权限
    http.authorizeRequests()
            // 地址/test/echo允许所有用户访问
            .antMatchers("/test/echo").permitAll()
            // 只有拥有ADMIN角色的用户才可以访问
            .antMatchers("/test/admin").hasRole("ADMIN")
            // 只有拥有NORMAL角色的用户才可以访问
            .antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')")
            // 访问任何请求的用户都需要认证,即都是登录过的用户
            .anyRequest().authenticated()
            // 设置使用默认的表单登录,并且所有用户可访问,可通过方法
            .and()
            .formLogin()
            .permitAll()
            // 设置退出登录页面,并设置所有人可访问
            .and()
            .logout()
            .permitAll();

}

antMatchers("/test/echo").permitAll()设置访问地址/test/echo可以随意访问,不受任何限制。antMatchers("/test/admin").hasRole("ADMIN")设置访问地址/test/admin必须拥有ADMIN角色才能访问。antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')")设置必须拥有NORMAL角色才能访问。anyRequest().authenticated()设置没有显示设置的访问地址都必须认证才能访问,即必须登录。最后的and().formLogin().permitAll()和and().logout().permitAll()是设置任何人都可以访问登录和退出登录。

3.1.2:TestController

源码如下:

@RestController
@RequestMapping("/test")
public class TestController {

    // 通过".antMatchers("/test/echo").permitAll(),"单独配置,所以该接口所有用户可访问
    @GetMapping("/echo")
    public String demo() {
        return "示例返回";
    }

    // 该接口并未单独设置,所有通过配置".anyRequest().authenticated()"访问该页面需要登录
    @GetMapping("/home")
    public String home() {
        return "我是首页";
    }

    // 该接口单独设置,设置为".antMatchers("/test/admin").hasRole("ADMIN")",所以登录用户必须有ADMIN角色才可以访问
    @GetMapping("/admin")
    public String admin() {
        return "我是管理员";
    }

    // 该接口单独设置,设置为".antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')")",所以必须有NORMAL角色才可以访问
    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }

}

如果是没有登录时,访问需要登录的接口则会跳转到登录页面,登录后访问没有权限访问的页面则会403,如下是使用normal用户登录后访问http://localhost:8049/test/admin:



如果是我们不重写protected void configure(HttpSecurity http)则默认的行为是访问所有的页面都必须认证,即需要登录,并使用默认的登录和退出登录页面,源码如下:

protected void configure(HttpSecurity http) throws Exception {
    this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
}

除了重写该方法进行自定义的访问限制之外,还可以通过注解的方式来实现,首先在配置类SecurityConfig添加注解@EnableGlobalMethodSecurity开启就有注解的配置,如下:

// 使其成为java config类
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {}

然后在TestController中添加相关的授权注解,修改如下:

@RestController
@RequestMapping("/test")
public class TestController {

    // 相当于代码设置".antMatchers("/test/echo").permitAll(),",所以该接口所有用户可访问
    @PermitAll
    @GetMapping("/echo")
    public String demo() {
        return "示例返回";
    }

    // 该接口并未单独设置,相当于使用默认的protected void configure(HttpSecurity http)方法的默认实现,即走所有请求都需要登录
    @GetMapping("/home")
    public String home() {
        return "我是首页";
    }

    // 相当于代码设置".antMatchers("/test/admin").hasRole("ADMIN")",登录用户必须有ADMIN角色才可以访问
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/admin")
    public String admin() {
        return "我是管理员";
    }

    // 相当于代码设置".antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')")",所以必须有NORMAL角色才可以访问
    @PreAuthorize("hasRole('ROLE_NORMAL')")
    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }

}

效果是完全相同的,大家可以自己试下。

4:自定义登录页面实例

在前面用到的是springsecurity提供的默认页面,本实例,我们从0开始看下如何自定义登录页面来完成认证和授权。

4.1:准备工作
  • 引入springmvc的起步依赖

    
        
            org.springframework.boot
            spring-boot-dependencies
            2.1.14.RELEASE
            pom
            import
        
    



    
    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-thymeleaf
    

  • 定义控制器
@Controller
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "index";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }

}
  • 定义相关页面

resources/index.html:




    Spring Security入门


欢迎使用Spring Security!

点击 这里 打个招呼吧

resources/hello.html:




    Hello World!


Hello [[${#httpServletRequest.remoteUser}]]!

此时项目结构如下:

  • 访问测试

点击这里:

可以看到此时的访问是不受任何控制的,随意访问。

4.2:整合springsecurity
  • 引入springsecurity起步依赖

    org.springframework.boot
    spring-boot-starter-security

访问http://localhost:8080/的话就会跳转到登录页面,如下:

因为默认的安全配置类org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(HttpSecurity),设置了任何请求都需要认证,即登录,源码如下:

// org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(HttpSecurity)
protected void configure(HttpSecurity http) throws Exception {
    logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin().and()
        .httpBasic();
}
  • 添加自定义的配置类
@Configuration
public class CustomLoginSecurityConfig extends WebSecurityConfigurerAdapter {
    // 配置http的访问控制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 允许访问/, /home
        http.authorizeRequests().antMatchers("/", "/home")
                // 所有其他请求都需要认证,即登录
                .permitAll().anyRequest().authenticated()
                .and()
                // 配置/login为登录地址,并允许所有人访问该地址
                .formLogin().loginPage("/login").permitAll()
                .and()
                // 使用默认的退出登录页面,并允许所有人访问
                .logout().permitAll();
    }

    // 配置认证授权使用的用户和角色
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password("user")
                .roles("USER");
    }
}
  • 添加登录页面

templates/login.html:




    Spring Security Example 


用户名或密码错
您已注销成功

此时项目结构如下:

  • 访问测试

http://localhost:8080/可以正常访问:

点击这里因为需要认证所以跳转到配置的登录页面:

输入账号密码user/user:

点击注销即可退出登录,并回到登录页面:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/444832.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号