spring-boot-note11 --- Uso de springsecurity

Prefácio:

   Spring Security é uma estrutura de gerenciamento de segurança da família Spring.Com base na estrutura Spring, Spring Security fornece uma solução completa para segurança de aplicativos da web. De modo geral, a segurança dos aplicativos da Web inclui duas partes: autenticação do usuário (Autenticação) e autorização do usuário (Autorização). A autenticação do usuário refere-se a verificar se um usuário é sujeito legal no sistema, ou seja, se o usuário pode acessar o sistema. A autenticação do usuário geralmente requer que o usuário forneça um nome de usuário e uma senha. O sistema conclui o processo de autenticação verificando o nome do usuário e a senha. A autorização do usuário refere-se a verificar se um usuário tem autoridade para realizar uma determinada operação. Em um sistema, diferentes usuários têm diferentes permissões. Por exemplo, para um arquivo, alguns usuários podem apenas lê-lo e alguns usuários podem modificá-lo. De modo geral, o sistema atribui funções diferentes a usuários diferentes, e cada função corresponde a uma série de permissões.

  O princípio é: crie muitos filtros e interceptores para verificar e interceptar solicitações, a fim de obter um efeito de segurança.

Vamos demonstrar por meio de alguns casos:

1. O uso de casos básicos

   1. Visão geral das funções básicas do caso

   O nome de usuário e senha configurados na memória são usados ​​para autenticação e autorização. Além disso, o link de configuração / admin / ** requer a função de administrador para acessar, e / usuário / ** requer a função de usuário para acessar. A classe java do caso é um mestre A classe de configuração é configurada com funções. Como o springsecurity tem um login / login padrão, o Controlador cria principalmente três métodos de solicitação: / admin / hello, / user / hello e / hello.

  2. Código

 Adicionar dependência de pom

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

Classe de configuração

Adicionar usuários que precisam ser fornecidos para autenticação por meio de configurar (AuthenticationManagerBuilder)

     Exemplo  auth.inMemoryAuthentication (). WithUser ("admin") .password ("admin") .roles ("admin", "usuário") // usuário de memória admin, senha admin, função admin, usuário

Através de configure (HttpSecurity http) para interceptar as permissões correspondentes do caminho e outras operações.

     Exemplo   e (). Logout (). LogoutUrl ("/ logout") .logoutSuccessHandler (new LogoutSuccessHandlers ()) // Adicionar processamento de saída personalizado.

           authorizeRequests ()
          .antMatchers ("/ usuário / **"). hasRole ("usuário") // a função do usuário é necessária no início do usuário
           antMatchers ("/ admin / **"). hasRole ("admin") / / no início de amdin Precisa de função de administrador

           e (). anyRequest (). authenticated () // Todos os caminhos precisam estar logados

           and (). csrf (). disable () // Desativa a função csrf padrão, caso contrário, o logout não pode ser usado para solicitar get e outros problemas semelhantes ocorrem.

@Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
	
	// 添加一个admin/admin role是 admin user  user/user role是user, guest, role 是guest 的静态用户数据
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication().withUser("admin")
				.password("admin") 
				.roles("admin", "user")
				.and().withUser("user") 
				.password("user").roles("user").and().withUser("guest") 
				.password("guest").roles();
	}
	
	// 配置路径的拦截 
	// produect  需要user权限
	// admin 需要admin权限
	protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").hasRole("user")  // user开头需要user的角色
                .antMatchers("/admin/**").hasRole("admin") // amdin开头需要admin的角色
                .anyRequest().authenticated() // 所有请求需要认证
                .and().formLogin()
                .and()
                .httpBasic()
                .and()
		        .logout() // 增加退出,指定路径是logout,退出处理结果在下面的内部类中。
		        .logoutUrl("/logout")
		        .logoutSuccessHandler(new LogoutSuccessHandlers())
		        .and().csrf().disable(); //  这里关闭csrf是为了logout生效(默认post)
    }
	
	private static class LogoutSuccessHandlers implements LogoutSuccessHandler {
		@Override
		public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
				Authentication authentication) throws IOException, ServletException {
			  response.setContentType("application/json;charset=utf-8");
              PrintWriter out = response.getWriter();
              out.write("退出登陆");
              out.flush();
		}
	}
	
}

Controlador

 

/**
 * 如果没有配置configure(AuthenticationManagerBuilder auth),默认密码:user , 启动时随机密码
 */
@RestController
public class HelloController {

	@RequestMapping("/admin/hello")
	public String adminhello() {
		return "admin hello";
	}
	
    @RequestMapping("/user/hello")
    public String producthello(){
        return "user hello";
    }
    
    @RequestMapping("/hello")
    public String hello() {
    	return "hello";
    }
}

  Uso de teste

   Esperado: Abra http: // localhost: 8080 / hello, ele irá pular automaticamente para a página / login, digite o nome de usuário e senha admin / admin, você pode visitar / hello, / admin / hello, / user / hello, se a senha está errado, / A página de login relatará um erro. Se você usar o grupo de senha de usuário usuário / usuário, não poderá acessá-lo, exceto / admin / hello.   

   Resultado: o teste atende às expectativas

Em segundo lugar, use o banco de dados para armazenar casos de usuários

    Quando realmente usamos o framework de segurança, é impossível configurar o nome de usuário e senha na memória. Neste momento, é para consultar os dados de acordo com o nome de usuário e senha logados. Se corresponder, o login foi bem-sucedido e as permissões são carregadas, etc., se não corresponder, retorna um erro. Em shiro, usamos domínio personalizado. No método doAuth, consultamos as informações do usuário com base no nome do usuário, retornamos informações personalizadas do usuário, senha, e configurar o método de criptografia da senha, sal, etc.

    Em spring-security, só precisamos fornecer um método loadUserByUsername (String username) que implemente a interface padrão UserDetailsService. Se o usuário for consultado, os dados da interface UserDetails serão montados. Nenhuma exceção é lançada e o resto será à esquerda para a estrutura. O sentido de uso é basicamente o mesmo do shiro, a diferença é que as informações do usuário e da função do spring-security são carregadas ao mesmo tempo.

  1. Configure nossa própria implementação de userDetailsService por meio de auth.userDetailsService (userDetailsService)

	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		 auth.userDetailsService(userDetailsService)
         .passwordEncoder(new BCryptPasswordEncoder());
	}

2 、 userDetailsService

@Component("userDetailsService")
public class UserDetailsServiceImple implements UserDetailsService{
	
	/** security中ROLE判断的默认前缀ROLE_, 你不加,它对比的时候会加。*/
	private static final String role_prefix = "ROLE_";
	
	private UserDao userDao = new UserDao();

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDao.findUserByUsername(username);
		if (user == null) {
			throw new UsernameNotFoundException("用户不存在!");
		}
		Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
		GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role_prefix+user.getRole());
		grantedAuthorities.add(grantedAuthority);
		return new org.springframework.security.core.userdetails.User(username, user.getPassword(), grantedAuthorities);
	}
	
}

3. UserDao Estes são dados de banco de dados simulados. Existem 2 usuários com função admin / admin = admin e função de usuário / usuário = função. Além disso, como usamos criptografia spring-security para criptografia, a senha para o banco de dados é necessária. Use criptografia spring-security para processamento .

/**
 * 数据库操作的模拟
 */
public class UserDao {
	
	private static Map<String,User> users = new HashMap<String,User>();
	
	static {
		User user1 = new User();
		user1.setUsername("admin"); // 密码是123456采用
		user1.setPassword("$2a$10$XLO0nZFBvLguTssPZdYr1ueQeiCYztmlKmh3J5XPLVOALuXRCzVX6");
		user1.setUsername("admin");
		user1.setRole("admin");
		User user2 = new User();
		user2.setUsername("user");
		user2.setPassword("$2a$10$XLO0nZFBvLguTssPZdYr1ueQeiCYztmlKmh3J5XPLVOALuXRCzVX6");
		user2.setRole("user");
		users.put(user1.getUsername(), user1);
		users.put(user2.getUsername(), user2);
	}
	
	public User findUserByUsername(String username) {
		User user = users.get(username);
		return user;
	}
	
	/**
	 * spring-security加密的测试。
	 */
	public static void main(String[] args) {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		String password = bCryptPasswordEncoder.encode("123456");
		System.out.println(password);
	}
	
}

4. No controlador, adicionamos principalmente o método de solicitação de / center.

    /**
     * SecurityContextHolder.getContext().getAuthentication().getPrincipal() 获取登陆用户 
     */
    @RequestMapping("/center")
	public String center() {
		String userDetails = null;
		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		if (principal instanceof UserDetails) {
			userDetails = ((UserDetails) principal).getUsername();
			userDetails = ((UserDetails) principal).getAuthorities().toString();
		} else {
			userDetails = principal.toString();
		}
		return userDetails;
	}

userDetails = SecurityContextHolder.getContext (). getAuthentication (). getPrincipal () são as informações de autenticação do usuário acessadas pelo método loadUserByUsername durante nossa autenticação .

5. Classe de configuração MySecurityConfiguration

/**
 * Security的配置类
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启支持注解方式的权限细粒度配置
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
		
	@Autowired
	private UserDetailsService userDetailsService;
	
	// 添加一个userDetailService通过数据库认证,并加载角色信息, 以及设置加密方法
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		 auth.userDetailsService(userDetailsService)
         .passwordEncoder(new BCryptPasswordEncoder());
	}
	
	// 配置路径的拦截 
	// produect  需要user权限
	// admin 需要admin权限
	protected void configure(HttpSecurity http) throws Exception {
		 http
         .authorizeRequests()
         .antMatchers("/user/**").hasRole("user")
         .antMatchers("/admin/**").hasRole("admin")
         .anyRequest().authenticated() //
         .and()
         .formLogin()
         .and()
         .httpBasic()
         .and().logout().logoutUrl("/logout")
         .and().csrf().disable();
    }
	
}

  Uso de teste

   Esperado: Abra http: // localhost: 8080 / hello, ele irá pular automaticamente para a página / login, digite o nome de usuário e senha do admin / admin, você pode acessar / hello, / user / hello, se a senha estiver errada, o A página / login relatará um erro, se o grupo de senha do usuário usuário / usuário for usado, / admin / hello não poderá ser acessado. Além disso, a página / center pode obter informações após a autenticação do usuário.

   Resultado: o teste atende às expectativas

Três, caminho JWT

  1. Operações principais

   Usando o método jwt, ainda somos os mesmos na autenticação, ou seja, usamos a senha original do usuário para fazer o login, mas não usamos a sessão para armazenar as informações do usuário

 http
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态session

  Então, depois de fazer o login, como podemos obter as informações de pós-autenticação para saber se o usuário está conectado? Ele usa principalmente um interceptor, que é executado antes do interceptor de autenticação padrão, e obtém a string de token na autenticação personalizada por meio de req.getHeader () (esse token é retornado ao front end para salvar depois de fazermos login com sucesso).

protected void configure(HttpSecurity http) throws Exception {
              .and().addFilterBefore(new JWTFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
public class JWTFilter extends GenericFilterBean {

	private final static String HEADER_AUTH_NAME = "auth";

	private JwtService jwtService;

	public JWTFilter(JwtService jwtService) {
		this.jwtService = jwtService;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		try {
			HttpServletRequest httpServletRequest = (HttpServletRequest) request;
			String authToken = httpServletRequest.getHeader(HEADER_AUTH_NAME);
			String parameterToken = httpServletRequest.getParameter(HEADER_AUTH_NAME);
			if(StringUtils.isNotEmpty(parameterToken)) { // 兼顾param上取token值,方便获取。
				Authentication authentication = jwtService.getAuthentication(parameterToken);
				if (authentication != null) {
					SecurityContextHolder.getContext().setAuthentication(authentication);
				}
			} else {
				if (StringUtils.isNotEmpty(authToken)) {
					Authentication authentication = jwtService.getAuthentication(authToken);
					if (authentication != null) {
						SecurityContextHolder.getContext().setAuthentication(authentication);
					}
				}
			}
			chain.doFilter(request, response);
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

}
	/**
	 * 认证成功处理
	 */
	private class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
		@Override
		public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
				Authentication authentication) throws IOException, ServletException {
			  PrintWriter writer = response.getWriter();
              writer.println(jwtService.createToken(authentication));
		}
	}

 2. A classe de operação de jwt

<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.4.0</version>
		</dependency>
public class JWTUtils {
	    
    /**
     * 私有key,加密是用户名密码的基础上再加上private_key
     */
    private static final String private_key = "abcdef";
    
    /**
     * token刷新时间 , 默认半小时
     */
    private static final long token_expire_time = 30 * 60 * 1000 ;
	
    public static String sign(String username, String secret) {
		Date date = new Date(System.currentTimeMillis() + token_expire_time);
		Algorithm algorithm = Algorithm.HMAC256(private_key + secret);
		return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
    }
    
    public static boolean verify(String token, String username, String secret) {
    	Algorithm algorithm = Algorithm.HMAC256(private_key + secret);
		JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
		try {
			DecodedJWT jwt = verifier.verify(token);
		} catch (JWTVerificationException e) {
			return false;
		}
		return true;
    }

    public static String getUsername(String token) {
    	return getClaim(token,"username");
    }
    
    public static String getClaim(String token,String key) {
    	  DecodedJWT jwt = JWT.decode(token);
          return jwt.getClaim(key).asString();
    }

}
/**
 * jwt认证相关
 */
@Service
public class JwtService {
	
	private UserDao userDao = new UserDao();

    public String createToken(Authentication authentication) {
    	String username = authentication.getName(); // 实际从userDetails中获取usernmae
    	User user = userDao.findUserByUsername(username);
    	String sign = JWTUtils.sign(username, user.getPassword());
    	return sign;
    }
    
    public Authentication getAuthentication(String token) {
		String username = JWTUtils.getUsername(token);
		User user = userDao.findUserByUsername(username);
		if (user == null) {
			return null;
		}
		boolean verify = JWTUtils.verify(token, username, user.getPassword());
		if(!verify) {
			return null;
		}
		MyUser myUser = new MyUser(user);
		Collection<? extends GrantedAuthority> authorities = myUser.getAuthorities();
		return new UsernamePasswordAuthenticationToken(user, token, authorities);
    }
	
}

3. Pojo e dao, etc., a classe UserDao permanece inalterada, a classe User permanece inalterada , o caso adiciona uma classe de wrapper de usuário de MyUser, que implementa UserDetails, o que não é necessário.

public class MyUser implements UserDetails {

	private String username;

	private String password;

	private String role;

	private static final long serialVersionUID = -601304311090873229L;

	public MyUser() {
	}

	public MyUser(User user) {
		this.username = user.getUsername();
		this.password = user.getPassword();
		this.role = user.getRole();
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		if (role != null) {
			return Collections.singleton(new SimpleGrantedAuthority("ROLE_"+this.role));
		}
		return Arrays.asList(new GrantedAuthority[0]);
	}

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

	@Override
	public String getUsername() {
		return this.username;
	}

	@Override
	public boolean isAccountNonExpired() {
		return false;
	}

	@Override
	public boolean isAccountNonLocked() {
		return false;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return false;
	}

	@Override
	public boolean isEnabled() {
		return false;
	}

}

4. Classe de configuração

@Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Autowired
	private UserDetailsService userDetailsService;
	
	@Autowired
	private JwtService jwtService;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		 auth.userDetailsService(userDetailsService)
         .passwordEncoder(new BCryptPasswordEncoder());
	}
	
	protected void configure(HttpSecurity http) throws Exception {
        http
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态session
            .and()
            .authorizeRequests()
            .antMatchers("/user/**").hasRole("user") 
            .antMatchers("/admin/**").hasRole("admin")
            .anyRequest().authenticated() 
            .and()
        	// 配置登陆和登陆成功的处理
            .formLogin().loginProcessingUrl("/login").successHandler(this.new MyAuthenticationSuccessHandler())
            .and().csrf().disable() 
            .httpBasic()
        	// 配置登出和登出处理器
            .and()
            .logout()
	        .logoutUrl("/logout")
	        .logoutSuccessHandler(new LogoutSuccessHandlers())
        	// 在UsernamePasswordAuthenticationFilter之前执行我们添加的JWTFilter
            .and().addFilterBefore(new JWTFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
    }
	
	@Override
    public void configure(WebSecurity web) {
        web.ignoring()
            .antMatchers("/swagger-resources/**")
            .antMatchers("/swagger-ui.html")
            .antMatchers("/webjars/**")
            .antMatchers("/v2/**")
            .antMatchers("/h2-console/**");
    }
	
	/**
	 * 认证成功处理
	 */
	private class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
		@Override
		public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
				Authentication authentication) throws IOException, ServletException {
			  PrintWriter writer = response.getWriter();
              writer.println(jwtService.createToken(authentication));
		}
	}
	
	/**
	 * 退出处理
	 */
	private static class LogoutSuccessHandlers implements LogoutSuccessHandler {
		@Override
		public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
				Authentication authentication) throws IOException, ServletException {
			  response.setContentType("application/json;charset=utf-8");
              PrintWriter out = response.getWriter();
              out.write("退出登陆");
              out.flush();
		}
	}
	
}

5. Teste e use

   Esperado: faça login com admin / 123456. Depois que o login for bem-sucedido, as informações do token serão retornadas e, em seguida, o token será retornado e, em seguida, visite / hello? Auth = token / admin / hello? Auth = token / user / hello? auth = token, o usuário administrador esperado Não pode acessar / user / hello.

   Resultado: o teste atende às expectativas

Quatro, anotação

@EnableGlobalMethodSecurity (prePostEnabled = true)   

 //hasRole和hasAuthority都会对UserDetails中的getAuthorities进行判断区别是hasRole会对字段加
    // 上ROLE_后再进行判断
    @RequestMapping("/test")
    @PreAuthorize("hasRole('admin')") // 需要admin的角色
   // @PreAuthorize("hasAuthority('ROLE_admin')") //  
    public String test() {
    	return "id";
    }

Cinco, a principal introdução de segurança de mola

	SecurityContext :安全的上下文,所有的数据都是保存到SecurityContext中,通过SecurityContext获取Authentication。
	SecurityContextHolder:用来获取SecurityContext中保存的数据的工具 SecurityContext context = SecurityContextHolder.getContext();
	Authentication:表示当前的认证情况,如UserDetails、Credentials、isAuthenticated、Principal。
		UserDetails:getPassword、getUsername等
	UserDetailsService:用来自己自定义查询UserDetails信息
	AuthenticationManager:AuthenticationManager用来进行验证,如果验证失败会抛出相对应的异常
	PasswordEncoder: 密码加密器
		BCryptPasswordEncoder:哈希算法加密
		NoOpPasswordEncoder:不使用加密

 

 

 

 

 

Acho que você gosta

Origin blog.csdn.net/shuixiou1/article/details/112910645
Recomendado
Clasificación