spring-boot-note11---Use of springsecurity

Foreword:

   Spring Security is a security management framework in the Spring family. Based on the Spring framework, Spring Security provides a complete solution for web application security. Generally speaking, the security of Web applications includes two parts: user authentication (Authentication) and user authorization (Authorization). User authentication refers to verifying whether a user is a legal subject in the system, that is, whether the user can access the system. User authentication generally requires the user to provide a user name and password. The system completes the authentication process by verifying the user name and password. User authorization refers to verifying whether a user has the authority to perform a certain operation. In a system, different users have different permissions. For example, for a file, some users can only read it, and some users can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions.

  The principle is: create a lot of filters and interceptors to verify and intercept the request, in order to achieve the effect of security.

Let's demonstrate through some cases:

1. The use of basic cases

   1. Overview of basic case functions

   The user name and password configured in the memory are used for authentication and authorization. In addition, the configuration link /admin/** requires the role of admin to access, and /user/** requires the role of user to access. The java class of the case is a master The configuration class is configured with functions. Because springsecurity has a default login/login, the Controller mainly creates three request methods: /admin/hello, /user/hello, and /hello.

  2. Code

 Add pom dependency

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

Configuration class

Add users that need to be provided for authentication through configure(AuthenticationManagerBuilder)

     Example  auth.inMemoryAuthentication().withUser("admin") .password("admin") .roles("admin", "user") // memory user admin, password admin, role admin, user

Through configure (HttpSecurity http) to intercept the corresponding permissions of the path and other operations.

     Example   and().logout().logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessHandlers()) // Add custom exit processing.

           authorizeRequests()
          .antMatchers("/user/**").hasRole("user") // user's role is required at the beginning of user
           antMatchers("/admin/**").hasRole("admin") // at the beginning of amdin Need admin role

           and().anyRequest().authenticated() // All paths need to be logged in

           and().csrf().disable() // Turn off the default csrf function, otherwise logout cannot be used to request get, and other similar problems occur.

@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();
		}
	}
	
}

Controller

 

/**
 * 如果没有配置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";
    }
}

  Test use

   Expected: Open http://localhost:8080/hello, it will automatically jump to the /login page, enter admin/admin username and password, you can visit /hello, /admin/hello, /user/hello, if the password is wrong, / The login page will report an error. If you use the user password group of user/user, you cannot access it except /admin/hello.   

   Result: the test meets expectations

Second, use the database to store user cases

    When we really use the security framework, it is impossible to configure the user name and password in the memory. At this time, it is to query the data according to the login user name and password. If it matches, the login is successful and the permissions are loaded, etc., if it does not match , It returns an error. In shiro, we use custom realm. In the doAuth method, we query user information based on the user name, return custom user information, password, and configure the encryption method of the password, salt, etc.

    In spring-security, we only need to provide a loadUserByUsername(String username) method that implements the standard interface UserDetailsService. If the user is queried, the data of the UserDetails interface will be assembled. No exception is thrown, and the rest will be left to the framework. The sense of use is basically the same as shiro, the difference is that the user and role information of spring-security is loaded at one time.

  1. Configure our own implementation of userDetailsService through 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 This is simulated database data. There are 2 users with admin/admin role=admin and user/user role=role. In addition, because we use spring-security encryption for encryption, the password for the database is required. Use spring-security encryption for processing .

/**
 * 数据库操作的模拟
 */
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. On the controller, we mainly added the request method of /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() is the user authentication information accessed by the loadUserByUsername method during our authentication .

5. Configuration class 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();
    }
	
}

  Test use

   Expected: Open http://localhost:8080/hello, it will automatically jump to the /login page, enter the admin/admin username and password, you can visit /hello, /user/hello, if the password is wrong, the /login page will report an error, If the user password group of user/user is used, /admin/hello cannot be accessed. In addition, the /center page can obtain information after user authentication.

   Result: the test meets expectations

Three, JWT way

  1. Main operations

   Using the jwt method, we are still the same in authentication, that is, use the original password user to log in, but do not use session to store user information

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

  So after we have logged in, how can we get the post-authentication information of whether the user is logged in? It mainly uses an interceptor, which runs before the default authentication interceptor, and obtains the token string in the custom auth through req.getHeader() (this token is returned to the front end to save after we successfully log in).

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. The operation class of 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 and dao, etc., the UserDao class remains unchanged, the User class remains unchanged , the case adds a user wrapper class of MyUser, which implements UserDetails, which is not necessary.

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. Configuration class

@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. Test and use

   Expected: Log in with admin/123456. After the login is successful, the token information will be returned, and then the token will be returned, and then visit /hello?auth=token /admin/hello?auth=token /user/hello?auth=token, the expected admin user Cannot access /user/hello.

   Result: the test meets expectations

Four, annotation

@EnableGlobalMethodSecurity(prePostEnabled = true)   

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

Five, the main introduction of spring-security

	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:不使用加密

 

 

 

 

 

Guess you like

Origin blog.csdn.net/shuixiou1/article/details/112910645