mail_yanpeng@163.com
4 lat temu
16 zmienionych plików z 442 dodań i 5 usunięć
@ -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"; |
|||
} |
|||
} |
@ -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"; |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
@ -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> |
Ładowanie…
Reference in new issue