Browse Source

动态授权完成

master
mail_yanpeng@163.com 3 years ago
parent
commit
4aaccf5565
  1. 2
      build.gradle
  2. 74
      src/main/java/com/aiprose/scauth/conf/WebSecurityConfig.java
  3. 13
      src/main/java/com/aiprose/scauth/controller/HomeController.java
  4. 13
      src/main/java/com/aiprose/scauth/controller/LoginController.java
  5. 21
      src/main/java/com/aiprose/scauth/entity/Menu.java
  6. 22
      src/main/java/com/aiprose/scauth/entity/RoleMenu.java
  7. 1
      src/main/java/com/aiprose/scauth/handler/LoginSuccessHandler.java
  8. 103
      src/main/java/com/aiprose/scauth/handler/RemeberMeHandler.java
  9. 58
      src/main/java/com/aiprose/scauth/handler/UrlRoleAuthHandler.java
  10. 40
      src/main/java/com/aiprose/scauth/handler/UrlRolesFilterHandler.java
  11. 18
      src/main/java/com/aiprose/scauth/repository/MenuRepository.java
  12. 20
      src/main/java/com/aiprose/scauth/repository/RoleMenuRepository.java
  13. 2
      src/main/java/com/aiprose/scauth/service/IRoleService.java
  14. 26
      src/main/java/com/aiprose/scauth/service/impl/RoleServiceImpl.java
  15. 4
      src/main/resources/application.yml
  16. 30
      src/main/resources/templates/login.html

2
build.gradle

@ -24,6 +24,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'

74
src/main/java/com/aiprose/scauth/conf/WebSecurityConfig.java

@ -1,12 +1,17 @@
package com.aiprose.scauth.conf;
import com.aiprose.scauth.entity.User;
import com.aiprose.scauth.handler.AuthLimitHandler;
import com.aiprose.scauth.handler.LoginSuccessHandler;
import com.aiprose.scauth.handler.*;
import com.aiprose.scauth.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -15,6 +20,11 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import java.util.ArrayList;
import java.util.List;
/**
* @author nelson
@ -29,6 +39,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IUserService userService;
@Autowired
private RemeberMeHandler remeberMeHandler;
@Override
public void configure(WebSecurity web) throws Exception {
// super.configure(web);
@ -38,19 +51,41 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// http.authorizeRequests().antMatchers("/user/save").permitAll().anyRequest();
// 配置记住我的参数和记住我处理类
http.rememberMe()
.tokenRepository(remeberMeHandler)
.tokenValiditySeconds(60*60*24)
.userDetailsService(userDetailsService());
// 配置登录页面
http.formLogin().loginPage("/login").permitAll();
// 配置登录成功后的操作
http.formLogin().successHandler(new LoginSuccessHandler());
//权限不足
http.exceptionHandling().accessDeniedHandler(new AuthLimitHandler());
super.configure(http);
// 登出授权
http.logout().permitAll();
// 授权配置
http.authorizeRequests()
/* 所有静态文件可以访问 */
.antMatchers("/js/**","/css/**","/images/**").permitAll()
/* 所有 以/ad 开头的 广告页面可以访问 */
.antMatchers("/ad/**").permitAll()
.antMatchers("/user/**","/role/**").permitAll()
/* 动态url权限 */
.withObjectPostProcessor(new DefinedObjectPostProcessor())
/* url决策 */
.accessDecisionManager(accessDecisionManager())
.anyRequest().authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
// super.configure(auth);
// auth.passwordEncoder(new BCryptPasswordEncoder());
// .withUser("nelson").password(new BCryptPasswordEncoder().encode("123456")).roles("admin")
// .and()
@ -74,4 +109,33 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
return user;
};
}
/**
* AffirmativeBased 任何一个AccessDecisionVoter返回同意则允许访问
* ConsensusBased 同意投票多于拒绝投票忽略弃权回答则允许访问
* UnanimousBased 每个投票者选择弃权或同意则允许访问
*
* 决策管理
*/
private AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
decisionVoters.add(new WebExpressionVoter());
decisionVoters.add(new AuthenticatedVoter());
decisionVoters.add(new RoleVoter());
/* 路由权限管理 */
decisionVoters.add(new UrlRoleAuthHandler());
return new UnanimousBased(decisionVoters);
}
@Autowired
private UrlRolesFilterHandler urlRolesFilterHandler;
class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(urlRolesFilterHandler);
return object;
}
}
}

13
src/main/java/com/aiprose/scauth/controller/HomeController.java

@ -0,0 +1,13 @@
package com.aiprose.scauth.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/home")
public String home(){
return "home";
}
}

13
src/main/java/com/aiprose/scauth/controller/LoginController.java

@ -0,0 +1,13 @@
package com.aiprose.scauth.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login(){
return "login";
}
}

21
src/main/java/com/aiprose/scauth/entity/Menu.java

@ -0,0 +1,21 @@
package com.aiprose.scauth.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* @author nelson
* @desc 角色表
* @company 北京中经网软件有限公司
* @date 2020/11/27 17:04
* @since 1.0
*/
@Data
@Entity
@Table(name = "sys_menu")
public class Menu extends IDEntity {
private String url;
}

22
src/main/java/com/aiprose/scauth/entity/RoleMenu.java

@ -0,0 +1,22 @@
package com.aiprose.scauth.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* @author nelson
* @desc 角色菜单关联表
* @company 北京中经网软件有限公司
* @date 2020/11/27 17:04
* @since 1.0
*/
@Data
@Entity
@Table(name="sys_role_menu")
public class RoleMenu extends IDEntity{
private Integer sysRoleId;
private Integer sysMenuId;
}

1
src/main/java/com/aiprose/scauth/handler/LoginSuccessHandler.java

@ -23,5 +23,6 @@ public class LoginSuccessHandler implements AuthenticationSuccessHandler {
System.out.println(authentication.getAuthorities());
System.out.println(authentication.getCredentials());
System.out.println(authentication.getPrincipal());
response.sendRedirect("/home");
}
}

103
src/main/java/com/aiprose/scauth/handler/RemeberMeHandler.java

@ -0,0 +1,103 @@
package com.aiprose.scauth.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class RemeberMeHandler implements PersistentTokenRepository {
/**
* token有效时间30天
*/
private static final Long TOKEN_VALID_DAYS = 15L;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void createNewToken(PersistentRememberMeToken token) {
String key = generateTokenKey(token.getSeries());
Map<String,String> map = new HashMap<>(8);
map.put("username", token.getUsername());
map.put("tokenValue", token.getTokenValue());
map.put("date", String.valueOf(token.getDate().getTime()));
stringRedisTemplate.opsForHash().putAll(key, map);
stringRedisTemplate.expire(key,TOKEN_VALID_DAYS, TimeUnit.DAYS);
saveUsernameAndSeries(token.getUsername(), token.getSeries());
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
String key = generateTokenKey(series);
Boolean hasSeries = stringRedisTemplate.hasKey(key);
if(hasSeries==null || !hasSeries){
return;
}
Map<String,String> map = new HashMap<>(4);
map.put("tokenValue", tokenValue);
map.put("date", String.valueOf(lastUsed.getTime()));
stringRedisTemplate.opsForHash().putAll(key, map);
stringRedisTemplate.expire(key, TOKEN_VALID_DAYS, TimeUnit.DAYS);
String username = stringRedisTemplate.opsForValue().get(generateUsernameAndSeriesKey(series));
saveUsernameAndSeries(username, series);
}
@Override
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
String key = generateTokenKey(seriesId);
Map hashKeyValues = stringRedisTemplate.opsForHash().entries(key);
if (hashKeyValues == null) {
return null;
}
Object username = hashKeyValues.get("username");
Object tokenValue = hashKeyValues.get("tokenValue");
Object date = hashKeyValues.get("date");
if (null == username || null == tokenValue || null == date) {
return null;
}
long timestamp = Long.valueOf(String.valueOf(date));
Date time = new Date(timestamp);
return new PersistentRememberMeToken(String.valueOf(username), seriesId, String.valueOf(tokenValue), time);
}
@Override
public void removeUserTokens(String username) {
String series = stringRedisTemplate.opsForValue().get(generateUsernameAndSeriesKey(username));
if (series==null||series.trim().length()<=0){
return;
}
stringRedisTemplate.delete(generateTokenKey(series));
stringRedisTemplate.delete(generateUsernameAndSeriesKey(username));
stringRedisTemplate.delete(generateUsernameAndSeriesKey(series));
}
private void saveUsernameAndSeries(String username, String series){
stringRedisTemplate.opsForValue().set(generateUsernameAndSeriesKey(username),series,TOKEN_VALID_DAYS*2, TimeUnit.DAYS);
stringRedisTemplate.opsForValue().set(generateUsernameAndSeriesKey(series),username,TOKEN_VALID_DAYS*2, TimeUnit.DAYS);
}
/**
* 生成token key
*/
private String generateTokenKey(String series) {
return "spring:security:rememberMe:token:" + series;
}
/**
* 生成key
*/
private String generateUsernameAndSeriesKey(String usernameOrSeries) {
return "spring:security:rememberMe:"+usernameOrSeries;
}
}

58
src/main/java/com/aiprose/scauth/handler/UrlRoleAuthHandler.java

@ -0,0 +1,58 @@
package com.aiprose.scauth.handler;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 角色 权限 路由处理
*/
public class UrlRoleAuthHandler implements AccessDecisionVoter<Object> {
@Override
public boolean supports(ConfigAttribute attribute) {
if (null == attribute.getAttribute()) {
return false;
}
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
/**
* ACCESS_GRANTED 同意
* ACCESS_DENIED 拒绝
* ACCESS_ABSTAIN 弃权
*/
@Override
public int vote(Authentication user, Object object, Collection<ConfigAttribute> urlRoles) {
if (null == user) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> userRoles = user.getAuthorities();
/* 遍历链接中对应的权限 */
for (ConfigAttribute urlRole : urlRoles) {
if (this.supports(urlRole)) {
/* 此处默认值为弃权表示只要有一个角色对应上用户就可以访问链接
如果值改为拒绝表示必须全部角色包含才能访问链接
*/
result = ACCESS_ABSTAIN;
/* 遍历用户中对应的角色列表 */
for (GrantedAuthority userRole : userRoles) {
if (urlRole.getAttribute().equals(userRole.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
}

40
src/main/java/com/aiprose/scauth/handler/UrlRolesFilterHandler.java

@ -0,0 +1,40 @@
package com.aiprose.scauth.handler;
import com.aiprose.scauth.service.IRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
@Service
public class UrlRolesFilterHandler implements FilterInvocationSecurityMetadataSource {
@Autowired
private IRoleService roleService;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<String> roleNames = roleService.findByUrl(requestUrl);
String[] names = new String[roleNames.size()];
for (int i = 0; i < roleNames.size(); i++) {
names[i] = roleNames.get(i);
}
return SecurityConfig.createList(names);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}

18
src/main/java/com/aiprose/scauth/repository/MenuRepository.java

@ -0,0 +1,18 @@
package com.aiprose.scauth.repository;
import com.aiprose.scauth.entity.Menu;
import com.aiprose.scauth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author nelson
* @desc TODO
* @company 北京中经网软件有限公司
* @date 2020/11/27 17:17
* @since 1.0
*/
public interface MenuRepository extends JpaRepository<Menu, Integer>, JpaSpecificationExecutor<Menu> {
Menu findByUrl(String url);
}

20
src/main/java/com/aiprose/scauth/repository/RoleMenuRepository.java

@ -0,0 +1,20 @@
package com.aiprose.scauth.repository;
import com.aiprose.scauth.entity.RoleMenu;
import com.aiprose.scauth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
/**
* @author nelson
* @desc TODO
* @company 北京中经网软件有限公司
* @date 2020/11/27 17:17
* @since 1.0
*/
public interface RoleMenuRepository extends JpaRepository<RoleMenu, Integer>, JpaSpecificationExecutor<RoleMenu> {
List<RoleMenu> findBySysMenuId(Integer menuId);
}

2
src/main/java/com/aiprose/scauth/service/IRoleService.java

@ -16,5 +16,7 @@ import java.util.List;
public interface IRoleService extends BaseSpecService<Role,Integer> {
List<Role> findByUserId(Integer userId);
List<String> findByUrl(String url);
UserRole save(Role role, Integer userId);
}

26
src/main/java/com/aiprose/scauth/service/impl/RoleServiceImpl.java

@ -1,8 +1,12 @@
package com.aiprose.scauth.service.impl;
import com.aiprose.base.BaseSpecServiceImpl;
import com.aiprose.scauth.entity.Menu;
import com.aiprose.scauth.entity.Role;
import com.aiprose.scauth.entity.RoleMenu;
import com.aiprose.scauth.entity.UserRole;
import com.aiprose.scauth.repository.MenuRepository;
import com.aiprose.scauth.repository.RoleMenuRepository;
import com.aiprose.scauth.repository.RoleRepository;
import com.aiprose.scauth.repository.UserRoleRepository;
import com.aiprose.scauth.service.IRoleService;
@ -28,6 +32,12 @@ public class RoleServiceImpl extends BaseSpecServiceImpl<RoleRepository, Role, I
@Autowired
private UserRoleRepository userRoleRepository;
@Autowired
private RoleMenuRepository roleMenuRepository;
@Autowired
private MenuRepository menuRepository;
@Override
public List<Role> findByUserId(Integer userId) {
List<UserRole> userRoles = userRoleRepository.findBySysUserId(userId);
@ -42,6 +52,22 @@ public class RoleServiceImpl extends BaseSpecServiceImpl<RoleRepository, Role, I
return new ArrayList<>();
}
@Override
public List<String> findByUrl(String url) {
Menu menu = menuRepository.findByUrl(url);
if(menu == null){
return new ArrayList<>();
}
List<RoleMenu> bySysMenuId = roleMenuRepository.findBySysMenuId(menu.getId());
if(bySysMenuId == null){
return new ArrayList<>();
}
List<Integer> roleIds = bySysMenuId.stream().map(x -> x.getSysRoleId()).collect(Collectors.toList());
List<Role> roles = repository.findAllById(roleIds);
List<String> roleNames = roles.stream().map(x -> x.getRole()).collect(Collectors.toList());
return roleNames;
}
@Override
public UserRole save(Role role, Integer userId) {
Role newRole = repository.save(role);

4
src/main/resources/application.yml

@ -1,6 +1,10 @@
server:
port: 9982
spring:
redis:
host: localhost
port: 6379
database: 1
jpa:
hibernate:
ddl-auto: update

30
src/main/resources/templates/login.html

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>请登录</title>
</head>
<body>
<div>
<form th:action="@{/login}" method="post" action="/login">
<p>
<span>用户名:</span>
<input type="text" id="username" name="username">
</p>
<p>
<span>密码:</span>
<input type="password" id="password" name="password">
</p>
<p>
<span>记住我:</span>
<input type="checkbox" name="remember-me" value="true">
</p>
<!-- 不使用 th:action 属性 和 不关闭csrf 的情况下,需要放开下面的标签 -->
<!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}"/>-->
<input type="submit" value="登录" />
</form>
</div>
</body>
</html>
Loading…
Cancel
Save