您的位置:首页 > 编程语言 > Java开发

spring boot spring security学习

2017-09-21 15:27 501 查看

spring boot spring security学习

目的在于搭建一个可以在生产使用,基于spring boot+spring security+thymeleaf开发的权限示例。

前段页面权限显示

url服务端权限验证

密码md5加密

登录日志

项目说明

/login /about /403 不需要任何权限

/admin 管理员权限

/user 用户权限 管理员权限

/test 测试系统无权限显示

项目依赖

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
<!-- hot swapping, disable cache for template, enable live reload -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- Optional, for bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>


Spring Security

/**
* <p>security权限配置</p>
* Created by zhezhiyong@163.com on 2017/9/20.
*/
@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
// 使用这个注解,可以开启security的注解,我们可以在需要控制权限的方法上面使用@PreAuthorize,@PreFilter这些注解。
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AccessDeniedHandler accessDeniedHandler;

@Override
@Bean
protected UserDetailsService userDetailsService() {
return new LightSwordUserDetailService();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return MD5Util.encode((String) charSequence);
}

@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(MD5Util.encode((String) charSequence));
}
}); //指定密码加密所使用的加密器为passwordEncoder()
auth.eraseCredentials(false);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().headers().disable()
.authorizeRequests()
.antMatchers("/login", "/about", "/403").permitAll()
.antMatchers("/admin/**").hasAnyAuthority("admin")
.antMatchers("/", "/user/**").hasAnyAuthority("admin", "user")
//其他地址的访问均需验证权限
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login") //指定登录页是"/login"
.defaultSuccessUrl("/") //登录成功后默认跳转到"/hello"
.failureUrl("/loginError")
.permitAll()
.successHandler(loginSuccessHandler()) //登录日志
.and()
.logout().logoutSuccessUrl("/login") //退出登录后的默认url是"/home"
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler);
}

@Override
public void configure(WebSecurity web) throws Exception {
// 默认不拦截静态资源的url pattern
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/webjars/**");
}

@Bean
public LoginSuccessHandler loginSuccessHandler(){
return new LoginSuccessHandler();
}
}


定义全局异常处理

/**
* <p>异常处理</p>
* Created by zhezhiyong@163.com on 2017/9/20.
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandlerAdvice {

@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String exception(final Throwable throwable, final Model model) {
//        log.error("Exception during execution of SpringSecurity application", throwable);
String errorMessage = (throwable != null ? throwable.getMessage() : "Unknown error");
model.addAttribute("errorMessage", errorMessage);
return "error";
}

}


当前只定义了http status 500 异常处理,如有需要可以自己定义其它

定义登录日志记录类

/**
* <p>登录日志</p>
* Created by zhezhiyong@163.com on 2017/9/21.
*/
@Slf4j
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
User user = (User) authentication.getPrincipal();
log.info("当前登录用户: {}, ip地址: {}", user.getUsername(), getIpAddress(request));
super.onAuthenticationSuccess(request, response, authentication);
}

public String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}


定义403权限访问,无权限记录日志并跳转

/**
* <p>定义403权限访问,无权限记录日志并跳转</p>
* Created by zhezhiyong@163.com on 2017/9/20.
*/
@Component
@Slf4j
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

if (auth != null) {
log.info("User '" + auth.getName()
+ "' attempted to access the protected URL: "
+ httpServletRequest.getRequestURI());
}
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
}
}


定义view跳转

/**
* <p>View的Controller</p>
* Created by zhezhiyong@163.com on 2017/9/20.
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

/**
* 统一注册纯RequestMapping跳转View的Controller
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/index");
registry.addViewController("/login").setViewName("/login");
registry.addViewController("/about").setViewName("/about");
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/403").setViewName("/error/403");
}
}


定义用户数据和权限,和数据库关联

/**
* <p>定义用户数据和权限,和数据库关联</p>
* Created by zhezhiyong@163.com on 2017/9/20.
*/
@Slf4j
public class LightSwordUserDetailService implements UserDetailsService {

@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private UserRoleDao userRoleDao;

@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userDao.findByUserName(userName);
if (null == user) throw new UsernameNotFoundException(userName + " not found");
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
List<UserRole> userRoleList = userRoleDao.findByUserId(user.getId());
userRoleList.forEach(userRole -> {
Role role = roleDao.findOne(userRole.getRoleId());
if (role.getRoleName() != null) authorityList.add(new SimpleGrantedAuthority(role.getRoleName()));
});
log.info("{}-{}", userName, JSON.toJSONString(authorityList));
return new org.springframework.security.core.userdetails.User(userName, user.getPassword(), authorityList);
}
}


md5加密算法

/**
* <p>md5加密算法</p>
* Created by zhezhiyong@163.com on 2017/9/21.
*/
public class MD5Util {

private static final String SALT = "salt";

public static String encode(String password) {
password = password + SALT;
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
throw new RuntimeException(e);
}
char[] charArray = password.toCharArray();
byte[] byteArray = new byte[charArray.length];

for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuilder hexValue = new StringBuilder();
for (byte md5Byte : md5Bytes) {
int val = ((int) md5Byte) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}

public static void main(String[] args) {
System.out.println(MD5Util.encode("666666"));
}

}


IndexController

/**
* <p>IndexController</p>
* Created by zhezhiyong@163.com on 2017/9/20.
*/
@Controller
public class IndexController {

@RequestMapping("/test")
public String test(){
return "test";
}

@RequestMapping("/loginError")
public String loginError(Model model){
model.addAttribute("loginError", true);
return "login";
}

@RequestMapping("/user")
@PreAuthorize("hasAnyAuthority('user', 'admin')")
public String user(){
return "user";
}

@RequestMapping("/testError")
@PreAuthorize("hasAuthority('admin','user')")
public String error(){
return "user";
}

@RequestMapping("/admin")
@PreAuthorize("hasAuthority('admin')")
public String admin(){
return "admin";
}

}


配置程序启动

@SpringBootApplication
public class SpringbootSpringsecurityApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootSpringsecurityApplication.class, args);
}
}


server:
port: 8080
spring:
application:
name: spring-security
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
datasource:                           # 指定数据源
platform: h2                        # 指定数据源类型
schema: classpath:schema.sql        # 指定h2数据库的建表脚本
data: classpath:data.sql            # 指定h2数据库的数据脚本
logging:                                # 配置日志级别,让hibernate打印出执行的SQL
level:
root: INFO
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE


drop table user if exists;
drop table role if exists;
drop table userrole if exists;
create table user (id bigint generated by default as identity, user_name varchar(40), password varchar(40));
create table role (id bigint generated by default as identity, role_name varchar(40));
create table user_role (id bigint generated by default as identity, user_id bigint(20), role_id bigint(20));


insert into user (id, user_name, password) values (1, 'admin', 'e97c15df9188958552f27736979e9a5d');
insert into user (id, user_name, password) values (2, 'dev', 'e97c15df9188958552f27736979e9a5d');
insert into user (id, user_name, password) values (3, 'test', 'e97c15df9188958552f27736979e9a5d');
insert into role (id, role_name) values (1, 'admin');
insert into role (id, role_name) values (2, 'user');
insert into user_role (id, user_id, role_id) values (1, 1, 1);
insert into user_role (id, user_id, role_id) values (2, 2, 1);
insert into user_role (id, user_id, role_id) values (3, 2, 2);
insert into user_role (id, user_id, role_id) values (4, 3, 2);


密码是:666666

定义页眉页脚

<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:fragment="header-css">
<!-- this is header-css -->
<link rel="stylesheet" type="text/css" href="webjars/bootstrap/3.3.7/css/bootstrap.min.css"/>
<link rel="stylesheet" th:href="@{/css/main.css}" href="/static/css/main.css"/>
</div>
</head>
<body>
<div th:fragment="header">
<!-- this is header -->
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/}">Spring Boot</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a th:href="@{/}">Home</a></li>
</ul>
</div>
</div>
</nav>
</div>

</body>
</html>


<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
</head>
<body>
<div th:fragment="footer">

<div class="container">

<footer>
<!-- this is footer -->
© 2017 www.ktvme.com
<span sec:authorize="isAuthenticated()">
| Logged user: <span sec:authentication="name"></span> |
Roles: <span sec:authentication="principal.authorities"></span> |
<a th:href="@{/logout}">Sign Out</a>
</span>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript" src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</footer>
</div>

</div>
</body>
</html>


定义登录界面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security Example </title>
<div th:replace="common/header :: header-css"/>
</head>
<body>

<div th:replace="common/header :: header"/>

<div class="container">

<div class="row" style="margin-top:20px">
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
<form th:action="@{/login}" method="post">
<fieldset>
<h1>Please Sign In</h1>
<!--错误回显-->
<div th:if="${loginError}">
<div class="alert alert-danger">
Invalid username and password.
</div>
</div>

<div class="form-group">
<input type="text" name="username" id="username" class="form-control input-lg"
placeholder="UserName" required="true" autofocus="true"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg"
placeholder="Password" required="true"/>
</div>

<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-lg btn-primary btn-block" value="Sign In"/>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
<!--<label><input type="checkbox" id="rememberme" name="remember-me"/> Remember Me</label>-->
</div>
</div>
</fieldset>
</form>
</div>
</div>

</div>

<div th:replace="common/footer :: footer"/>

</body>
</html>


定义主页面

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Boot Thymeleaf + Spring Security</title>

<div th:replace="common/header :: header-css"/>

</head>
<body>

<div th:replace="common/header :: header"/>

<div class="container">

<div class="starter-template">
<h1>Spring Boot Web Thymeleaf + Spring Security</h1>
<h2>1. Visit <a th:href="@{/admin}">Admin page (Spring Security protected, Need Admin Role)</a></h2>
<h2>2. Visit <a th:href="@{/user}">User page (Spring Security protected, Need User Role)</a></h2>
<h2>2. Visit <a th:href="@{/testError}">Error page (Spring Security protected, Need User Role)</a></h2>
<h2>3. Visit <a th:href="@{/about}">Normal page</a></h2>
</div>

</div>
<!-- /.container -->

<div th:replace="common/footer :: footer"/>

</body>
</html>


定义管理员,用户,测试错误403界面,关于界面

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="common/header :: header-css"/>
</head>
<body>

<div th:replace="common/header :: header"/>

<div class="container">

<div class="starter-template">
<h1>Admin page (Spring Security protected)</h1>

<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>

<h1><a th:href="@{/index}">返回主页</a></h1>
</div>

</div>
<!-- /.container -->

<div th:replace="common/footer :: footer"/>

</body>
</html>


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="common/header :: header-css"/>
</head>
<body>

<div th:replace="common/header :: header"/>

<div class="container">

<div class="starter-template">
<h1>User page (Spring Security protected)</h1>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<h1><a th:href="@{/index}">返回主页</a></h1>
</div>

</div>
<!-- /.container -->
<div th:replace="common/footer :: footer"/>

</body>
</html>


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="common/header :: header-css"/>
</head>
<body>

<div th:replace="common/header :: header"/>

<div class="container">

<div class="starter-template">
<h1>403 - Access is denied</h1>
<div th:inline="text">Hello '[[${#httpServletRequest.remoteUser}]]', you do not have permission to access this page.</div>
</div>

</div>
<!-- /.container -->

<div th:replace="common/footer :: footer"/>

</body>
</html>


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="common/header :: header-css"/>
</head>
<body>

<div th:replace="common/header :: header"/>

<div class="container">

<div class="starter-template">
<h1>Normal page (No need login)</h1>

<h1><a th:href="@{/index}">返回主页</a></h1>
</div>

</div>
<!-- /.container -->

<div th:replace="common/footer :: footer"/>

</body>
</html>


css样式

h1 {
color: #0000FF;
}

h2 {
color: #FF0000;
}

footer {
margin-top: 60px;
}


示例



源码在这下载,加Star亦可

参考

spring-boot-spring-security-thymeleaf-example

springsecurity4+springboot 实现remember-me

Spring boot+Spring Security 4配置整合实例

springBoot+springSecurity验证密码MD5加密
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息