作者 朱兆平

spring security 用户登录部分完善

... ... @@ -92,6 +92,12 @@
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- oracle-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
... ...
... ... @@ -11,11 +11,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@SpringBootApplication
@EnableScheduling
@EnableEurekaClient
//@EnableEurekaClient
@MapperScan("com.tianbo.warehouse.dao")
public class WarehouseApplication {
... ...
... ... @@ -15,9 +15,8 @@ public class MainController {
UserService userService;
@GetMapping("/error")
public List<USERS> error(){
List<USERS> usersList =userService.selectAllUser();
return usersList;
public String error(){
return "error";
}
@GetMapping("/main")
... ...
... ... @@ -136,26 +136,47 @@ public class USERS implements UserDetails{
public void setPermissions(List<PERMISSION> permissions) {
this.permissions = permissions;
}
/**
* 账户未过期
* @return
*/
@Override
public boolean isAccountNonExpired(){
return true;
}
/**
* 账户未锁定
* @return
*/
@Override
public boolean isAccountNonLocked(){
return true;
}
/**密码未过期
*
* @return
*/
@Override
public boolean isCredentialsNonExpired(){
return true;
}
/**
* //账户可用
* @return
*/
@Override
public boolean isEnabled(){
return true;
}
/**
* user的权限列表
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities(){
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
... ...
... ... @@ -3,6 +3,7 @@ package com.tianbo.warehouse.security;
import com.tianbo.warehouse.model.USERS;
import com.tianbo.warehouse.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
... ... @@ -20,13 +21,13 @@ public class CustomUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
USERS user = userService.loadByUsername(username);
return user;
} catch (UsernameNotFoundException e) {
e.printStackTrace();
USERS user = userService.loadByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
// throw new BadCredentialsException("用户名不存在");
}
return null;
return user;
}
}
... ...
... ... @@ -13,12 +13,17 @@ import java.util.Iterator;
@Service
public class MyAccessDecisionManager implements AccessDecisionManager{
// decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
/**
* decide方法接收三个参数,decide 方法是判定是否拥有权限的决策方法
* 其中第一个参数中保存了当前登录用户的角色信息,authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
* object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* 第三个参数则是MyInvocationSecurityMetadataSourceService中的getAttributes方法传来的,表示当前请求需要的角色(可能有多个),此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
* @param authentication
* @param object
* @param configAttributes
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException{
... ... @@ -37,7 +42,7 @@ public class MyAccessDecisionManager implements AccessDecisionManager{
}
}
}
throw new AccessDeniedException("no right");
throw new AccessDeniedException("权限不足!");
}
@Override
... ...
... ... @@ -13,6 +13,10 @@ import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* 本类的主要功能就是通过当前的请求地址,获取该地址需要的用户角色
* 如果getAttributes(Object o)方法返回null的话,意味着当前这个请求不需要任何角色就能访问,甚至不需要登录。
*/
@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource{
... ...
package com.tianbo.warehouse.security;
package com.tianbo.warehouse.security.config;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* PasswordEncoder 是个接口,该接口下有两个方法,一个是encoder 一个是matches,前者用于加密,后者用于匹配校验.
*/
@Service(value = "passwordEncodeService")
public class PasswordEncoderImp implements PasswordEncoder{
... ...
package com.tianbo.warehouse.security.config;
import com.tianbo.warehouse.security.model.BrowserProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "tianbo.security")
@Component
public class SecurityProperties {
/**
* 浏览器 属性类
*/
private BrowserProperties browser = new BrowserProperties();
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}
... ...
package com.tianbo.warehouse.security;
package com.tianbo.warehouse.security.config;
import com.tianbo.warehouse.security.handel.MyAuthenticationFailHandler;
import com.tianbo.warehouse.security.handel.MyAuthenticationSuccessHandler;
import com.tianbo.warehouse.security.MyFilterSecurityInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@Configuration
@EnableWebSecurity
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
... ... @@ -21,6 +25,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private MyAuthenticationSuccessHandler successHandler;
@Autowired
private MyAuthenticationFailHandler failHandler;
@Qualifier("customuserservice")
@Autowired
private UserDetailsService userDetailsService;
... ... @@ -41,11 +51,15 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.formLogin()
.passwordParameter("password")
.usernameParameter("username")
//.loginProcessingUrl("/home")//登陆提交的处理url
//登陆提交的处理url
//.loginProcessingUrl("/home")
.loginPage("/login")
.failureUrl("/error")
.permitAll()//登录页面用户任意访问
.successForwardUrl("/main")
.successHandler(successHandler)
.failureHandler(failHandler)
// .failureUrl("/error")
//登录页面用户任意访问
.permitAll()
// .successForwardUrl("/main")
.and()
.logout()
.logoutSuccessUrl("/?logout=true")
... ...
package com.tianbo.warehouse.security.handel;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tianbo.warehouse.security.config.SecurityProperties;
import com.tianbo.warehouse.security.model.LoginType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录失败处理器
* Created by mrz.
*/
@Component
public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
/**
* json 转换工具类
*/
@Autowired
private ObjectMapper objectMapper;
private String defaultFailureUrl;
private boolean forwardToDestination = false;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
//如果securityProperties中配置的是JSON就返回JSON
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
//设置状态码
response.setStatus(HttpStatus.UNAUTHORIZED.value());
//将 登录失败 信息打包成json格式返回
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
}
if (this.defaultFailureUrl == null) {
this.logger.debug("No failure URL set, sending 401 Unauthorized error");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
} else {
this.saveException(request, exception);
if (this.forwardToDestination) {
this.logger.debug("Forwarding to " + this.defaultFailureUrl);
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
} else {
this.logger.debug("Redirecting to " + this.defaultFailureUrl);
this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
}
}
}
}
... ...
package com.tianbo.warehouse.security.handel;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tianbo.warehouse.security.model.LoginType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.tianbo.warehouse.security.config.SecurityProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录成功后的返回处理
*/
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
protected final Log logger = LogFactory.getLog(this.getClass());
@Autowired
private ObjectMapper objectMapper;
private RequestCache requestCache = new HttpSessionRequestCache();
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
logger.info("登录成功");
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
//将 authention 信息打包成json格式返回
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}else {
//走原来的处理流程
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
} else {
String targetUrlParameter = this.getTargetUrlParameter();
if (!this.isAlwaysUseDefaultTargetUrl() && (targetUrlParameter == null || !StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
this.clearAuthenticationAttributes(request);
String targetUrl = savedRequest.getRedirectUrl();
this.logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
this.getRedirectStrategy().sendRedirect(request, response, targetUrl);
} else {
this.requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
}
}
... ...
package com.tianbo.warehouse.security.model;
/**
* browser(浏览器)配置文件里的: fantJ.security.browser.loginPage 属性类
*
*/
public class BrowserProperties {
/**
* loginPage 默认值 是login.html
* 如果 application.properties 里有对 fantJ.security.browser.loginPage 的声明,则获取该值
*/
private String loginPage = "/browser-login.html";
/**
* 默认 返回 json 类型,声明登录返回格式
*/
private LoginType loginType = LoginType.JSON;
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
public LoginType getLoginType() {
return loginType;
}
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
}
... ...
package com.tianbo.warehouse.security.model;
public enum LoginType {
REDIRECT,
JSON
}
... ...
... ... @@ -51,14 +51,9 @@ public class UserServiceImpl implements UserService{
List<USERS> list = usersMapper.selectAllUser();
for (USERS user: list) {
List<PERMISSION> permissionList = permissionMapper.findByUserId(user.getUserId());
if (permissionList!=null && permissionList.size()>0){
user.setPermissions(permissionList);
}
user.setPermissions(permissionList);
List<ROLE> roleList = roleMapper.findRolesByUserId(user.getUserId());
if (roleList!=null && roleList.size()>0){
user.setRoles(roleList);
}
user.setRoles(roleList);
}
return list;
}
... ...
... ... @@ -2,29 +2,46 @@
#服务端口
server.port=7003
server.servlet.context-path=${SERVER_CONTEXTPATH:}
#eureka主机名,会在控制页面中显示
eureka.instance.hostname=${spring.cloud.client.ip-address}
#eureka服务器页面中status的请求路径
eureka.instance.status-page-url=http://${eureka.instance.hostname}:${server.port}/index
#eureka注册中心服务器地址
eureka.client.service-url.defaultZone=http://10.50.3.82:19527/eureka/
#服务名
spring.application.name=imf-warehouse-reader
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
eureka.client.registry-fetch-interval-seconds=30
eureka.instance.lease-renewal-interval-in-seconds=15
eureka.instance.lease-expiration-duration-in-seconds=45
spring.application.name=tianbo.base.dev.devkit
#springcloud 基本配置
#eureka主机名,会在控制页面中显示
spring.cloud.features.enabled=false
spring.cloud.discovery.enabled=false
spring.cloud.service-registry.auto-registration.enabled=false
#eureka.instance.hostname=${spring.cloud.client.ip-address}
#eureka服务器页面中status的请求路径
#eureka.instance.status-page-url=http://${eureka.instance.hostname}:${server.port}/index
#eureka注册中心服务器地址
#eureka.client.service-url.defaultZone=http://10.50.3.82:19527/eureka/
#eureka.instance.prefer-ip-address=true
#eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#eureka.client.registry-fetch-interval-seconds=30
#eureka.instance.lease-renewal-interval-in-seconds=15
#eureka.instance.lease-expiration-duration-in-seconds=45
#spring.datasource.name=CGOETL
#spring.datasource oracle
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@10.50.3.68:1521:CGODW
spring.datasource.username=CGOETL
spring.datasource.password=1q2w3e4r
#spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
#spring.datasource.url=jdbc:oracle:thin:@10.50.3.68:1521:CGODW
#spring.datasource.username=CGOETL
#spring.datasource.password=1q2w3e4r
#spring datasource mysql
spring.datasource.url=jdbc:mysql://127.0.0.1:3307/statistics
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
#配置初始化大小/最小/最大
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
... ... @@ -35,25 +52,31 @@ spring.datasource.druid.max-wait=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
#间隔多久进行一次检测,检测需要关闭的空闲连接
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.validation-query=SELECT 'x' FROM DUAL
#oracle
#spring.datasource.druid.validation-query=SELECT 'x' FROM DUAL
#mysql
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.default-auto-commit=true
#security配置
trace=true
mybatis.mapper-locations=classpath:mapping/*.xml
mybatis.type-aliases-package=com.tianbo.warehouse.model
logging.level.com.tianbo.warehouse.dao=DEBUG
logging.level.org.springframework.security =debug
debug=true
pagehelper.helper-dialect=oracle
pagehelper.helper-dialect=mysql
#pagehelper.auto-dialect=true
#pagehelper.auto-runtime-dialect=true
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
#debug配置
trace=false
logging.level.com.tianbo.warehouse.dao=DEBUG
logging.level.org.springframework.security =trace
debug=false
\ No newline at end of file
... ...
... ... @@ -30,23 +30,7 @@
</div>
</nav>
<div class="container">
<div class="starter-template">
<p th:if="${param.logout}" class="bg-warning">已成功注销</p><!-- 1 -->
<p th:if="${param.error}" class="bg-danger">有错误,请重试</p> <!-- 2 -->
<h2>使用账号密码登录</h2>
<form name="form" th:action="@{/login}" action="/login" method="POST"> <!-- 3 -->
<div class="form-group">
<label for="username">账号</label>
<input type="text" class="form-control" name="username" value="" placeholder="账号" />
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" placeholder="密码" />
</div>
<input type="submit" id="login" value="Login" class="btn btn-primary" />
</form>
</div>
welcom
</div>
</body>
</html>
\ No newline at end of file
... ...