spring-boot: 2.1.4.RELEASE
spring-security-oauth3: 2.3.3.RELEASE (If you want to use the source code, do not change this version number at will, because The writing method from 2.4 up is different)
mysql: 5.7
Only postman is used for testing here, and the front-end page is not used for docking yet. Next There will be a page display for the permission allocation of the version role menu
1. Access the open interface http://localhost:7000/open/hello
2 . Access the protected interface http://localhost:7000/admin/user/info
## without a token. 3. Obtain the token after logging in, bring the token to access, and return successfully. The current login user information realizeoauth3 has four modes in total, which will not be explained here. Yes, I searched online and found the same thingBecause now we only consider unilateral applications, we use the password mode. There will be an article on SpringCloud Oauth3 later, gateway authenticationLet’s talk about a few points1. Interceptor configuration dynamic permissions Create a new MySecurityFilter class, inherit AbstractSecurityInterceptor, and implement the Filter interfaceInitialization, custom access decision manager@PostConstruct public void init(){ super.setAuthenticationManager(authenticationManager); super.setAccessDecisionManager(myAccessDecisionManager); }Custom filter calls security metadata Source
@Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.mySecurityMetadataSource; }Let’s first look at the core code of the custom filter calling the secure metadata sourceThe following code is used to obtain the permissions (roles) required for the current request to come in
/** * 获得当前请求所需要的角色 * @param object * @return * @throws IllegalArgumentException */ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) object).getRequestUrl(); if (IS_CHANGE_SECURITY) { loadResourceDefine(); } if (requestUrl.indexOf("?") > -1) { requestUrl = requestUrl.substring(0, requestUrl.indexOf("?")); } UrlPathMatcher matcher = new UrlPathMatcher(); List<Object> list = new ArrayList<>(); //无需权限的,直接返回 list.add("/oauth/**"); list.add("/open/**"); if(matcher.pathsMatchesUrl(list,requestUrl)) return null; Set<String> roleNames = new HashSet(); for (Resc resc: resources) { String rescUrl = resc.getResc_url(); if (matcher.pathMatchesUrl(rescUrl, requestUrl)) { if(resc.getParent_resc_id() != null && resc.getParent_resc_id().intValue() == 1){ //默认权限的则只要登录了,无需权限匹配都可访问 roleNames = new HashSet(); break; } Map map = new HashMap(); map.put("resc_id", resc.getResc_id()); // 获取能访问该资源的所有权限(角色) List<RoleRescDTO> roles = roleRescMapper.findAll(map); for (RoleRescDTO rr : roles) roleNames.add(rr.getRole_name()); } } Set<ConfigAttribute> configAttributes = new HashSet(); for(String roleName:roleNames) configAttributes.add(new SecurityConfig(roleName)); log.debug("【所需的权限(角色)】:" + configAttributes); return configAttributes; }Let’s take a look at the core code of the custom access decision manager. This code is mainly used to determine whether the currently logged in user (the role owned by the currently logged in user will be written in the last item) has the permission role
@Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(configAttributes == null){ //属于白名单的,不需要权限 return; } Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while (iterator.hasNext()){ ConfigAttribute configAttribute = iterator.next(); String needPermission = configAttribute.getAttribute(); for (GrantedAuthority ga: authentication.getAuthorities()) { if(needPermission.equals(ga.getAuthority())){ //有权限,可访问 return; } } } throw new AccessDeniedException("没有权限访问"); }2. Customize authentication exceptions to return common results Why is this needed? If this is not configured, it will be difficult for the front end and the back end to understand the content returned by the authentication failure. It is not possible yet. Unified interpretation, without further ado, let’s first look at the return situation without configuration and configuration (1) Before customization, when accessing the protected API interface without a token, the returned result is as follows ’s # (2) Let’s stipulate that after the interface that fails to authenticate returns to the interface, it will become the following. Is it better for us to process and prompt the user Okay, let’s take a look at where to configure itOur resource server OautyResourceConfig, rewrite the following part of the code to customize the authentication The result returned by the exceptionYou can refer to this https://www.yisu.com/article/131668.htm
@Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.authenticationEntryPoint(authenticationEntryPoint) //token失效或没携带token时 .accessDeniedHandler(requestAccessDeniedHandler); //权限不足时 }3. Get the current Login userFirst method: Use JWT to carry user information, and then parse after getting the tokenNo explanation for nowSecond method: Write a SecurityUser to implement the UserDetails interface (This is the one used in this project) The original only UserDetails interface only has username and password. Here we add the User in our system
protected User user; public SecurityUser(User user) { this.user = user; } public User getUser() { return user; }In BaseController, each Controller will Inheriting this, we write the getUser() method in it. As long as the user brings a token to access, we can directly obtain the information of the currently logged in user.
protected User getUser() { try { SecurityUser userDetails = (SecurityUser) SecurityContextHolder.getContext().getAuthentication() .getPrincipal(); User user = userDetails.getUser(); log.debug("【用户:】:" + user); return user; } catch (Exception e) { } return null; }So after the user logs in successfully, how to get the user The role collection, etc., here we need to implement the UserDetailsService interface
@Service public class TokenUserDetailsService implements UserDetailsService{ @Autowired private LoginService loginService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = loginService.loadUserByUsername(username); //这个我们拎出来处理 if(Objects.isNull(user)) throw new UsernameNotFoundException("用户名不存在"); return new SecurityUser(user); } }Then set the UserDetailsService in our security configuration class to the one we wrote above
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); }Finally we only need to implement our method in loginService, and judge whether the user exists based on our actual business processing, etc.
@Override public User loadUserByUsername(String username){ log.debug(username); Map map = new HashMap(); map.put("username",username); map.put("is_deleted",-1); User user = userMapper.findByUsername(map); if(user != null){ map = new HashMap(); map.put("user_id",user.getUser_id()); //查询用户的角色 List<UserRoleDTO> userRoles = userRoleMapper.findAll(map); user.setRoles(listRoles(userRoles)); //权限集合 Collection<? extends GrantedAuthority> authorities = merge(userRoles); user.setAuthorities(authorities); return user; } return null; }The database file is in this
The above is the detailed content of How does SpringBoot integrate SpringSecurityOauth2 to implement dynamic permission issues for authentication?. For more information, please follow other related articles on the PHP Chinese website!